| //! Anything related to the adapter API (IBluetooth). |
| |
| use bt_topshim::btif::{ |
| BaseCallbacks, BaseCallbacksDispatcher, BluetoothInterface, BluetoothProperty, BtAclState, |
| BtAddrType, BtBondState, BtConnectionDirection, BtConnectionState, BtDeviceType, BtDiscMode, |
| BtDiscoveryState, BtHciErrorCode, BtPinCode, BtPropertyType, BtScanMode, BtSspVariant, BtState, |
| BtStatus, BtThreadEvent, BtTransport, BtVendorProductInfo, DisplayAddress, DisplayUuid, |
| RawAddress, ToggleableProfile, Uuid, INVALID_RSSI, |
| }; |
| use bt_topshim::profiles::gatt::GattStatus; |
| use bt_topshim::profiles::hfp::EscoCodingFormat; |
| use bt_topshim::profiles::hid_host::{ |
| BthhConnectionState, BthhHidInfo, BthhProtocolMode, BthhReportType, BthhStatus, HHCallbacks, |
| HHCallbacksDispatcher, HidHost, |
| }; |
| use bt_topshim::profiles::sdp::{BtSdpRecord, Sdp, SdpCallbacks, SdpCallbacksDispatcher}; |
| use bt_topshim::profiles::ProfileConnectionState; |
| use bt_topshim::{controller, metrics, sysprop, topstack}; |
| |
| use bt_utils::array_utils; |
| use bt_utils::cod::{is_cod_hid_combo, is_cod_hid_keyboard}; |
| use bt_utils::uhid::UHid; |
| use btif_macros::{btif_callback, btif_callbacks_dispatcher, log_cb_args}; |
| |
| use log::{debug, error, warn}; |
| use num_derive::{FromPrimitive, ToPrimitive}; |
| use num_traits::cast::ToPrimitive; |
| use num_traits::pow; |
| use std::collections::{HashMap, HashSet}; |
| use std::convert::TryInto; |
| use std::fs::{File, OpenOptions}; |
| use std::hash::Hash; |
| use std::io::Write; |
| use std::os::fd::AsRawFd; |
| use std::process; |
| use std::sync::{Arc, Condvar, Mutex}; |
| use std::time::{Duration, Instant}; |
| use tokio::sync::mpsc::Sender; |
| use tokio::task::JoinHandle; |
| use tokio::time; |
| |
| use crate::bluetooth_admin::BluetoothAdminPolicyHelper; |
| use crate::bluetooth_gatt::{ |
| BluetoothGatt, GattActions, IBluetoothGatt, IScannerCallback, ScanResult, |
| }; |
| use crate::bluetooth_media::{BluetoothMedia, MediaActions, LEA_UNKNOWN_GROUP_ID}; |
| use crate::callbacks::Callbacks; |
| use crate::socket_manager::SocketActions; |
| use crate::uuid::{Profile, UuidHelper}; |
| use crate::{make_message_dispatcher, APIMessage, BluetoothAPI, Message, RPCProxy, SuspendMode}; |
| |
| pub(crate) const FLOSS_VER: u16 = 0x0001; |
| 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); |
| |
| const PID_DIR: &str = "/var/run/bluetooth"; |
| |
| const DUMPSYS_LOG: &str = "/tmp/dumpsys.log"; |
| |
| /// Represents various roles the adapter supports. |
| #[derive(Debug, FromPrimitive, ToPrimitive)] |
| #[repr(u32)] |
| pub enum BtAdapterRole { |
| Central = 0, |
| Peripheral, |
| CentralPeripheral, |
| } |
| /// Defines the adapter API. |
| pub trait IBluetooth { |
| /// Adds a callback from a client who wishes to observe adapter events. |
| fn register_callback(&mut self, callback: Box<dyn IBluetoothCallback + Send>) -> u32; |
| |
| /// Removes registered callback. |
| fn unregister_callback(&mut self, callback_id: u32) -> bool; |
| |
| /// Adds a callback from a client who wishes to observe connection events. |
| fn register_connection_callback( |
| &mut self, |
| callback: Box<dyn IBluetoothConnectionCallback + Send>, |
| ) -> u32; |
| |
| /// Removes registered callback. |
| fn unregister_connection_callback(&mut self, callback_id: u32) -> bool; |
| |
| /// Inits the bluetooth interface. Should always be called before enable. |
| fn init(&mut self, hci_index: i32) -> bool; |
| |
| /// Enables the adapter. |
| /// |
| /// Returns true if the request is accepted. |
| fn enable(&mut self) -> bool; |
| |
| /// Disables the adapter. |
| /// |
| /// Returns true if the request is accepted. |
| fn disable(&mut self) -> bool; |
| |
| /// Cleans up the bluetooth interface. Should always be called after disable. |
| fn cleanup(&mut self); |
| |
| /// Returns the Bluetooth address of the local adapter. |
| fn get_address(&self) -> RawAddress; |
| |
| /// Gets supported UUIDs by the local adapter. |
| fn get_uuids(&self) -> Vec<Uuid>; |
| |
| /// Gets the local adapter name. |
| fn get_name(&self) -> String; |
| |
| /// Sets the local adapter name. |
| fn set_name(&self, name: String) -> bool; |
| |
| /// Gets the bluetooth class. |
| fn get_bluetooth_class(&self) -> u32; |
| |
| /// Sets the bluetooth class. |
| fn set_bluetooth_class(&self, cod: u32) -> bool; |
| |
| /// Returns whether the adapter is discoverable. |
| fn get_discoverable(&self) -> bool; |
| |
| /// Returns the adapter discoverable timeout. |
| fn get_discoverable_timeout(&self) -> u32; |
| |
| /// Sets discoverability. If discoverable, limits the duration with given value. |
| fn set_discoverable(&mut self, mode: BtDiscMode, duration: u32) -> bool; |
| |
| /// Returns whether multi-advertisement is supported. |
| /// A minimum number of 5 advertising instances is required for multi-advertisment support. |
| fn is_multi_advertisement_supported(&self) -> bool; |
| |
| /// Returns whether LE extended advertising is supported. |
| fn is_le_extended_advertising_supported(&self) -> bool; |
| |
| /// Starts BREDR Inquiry. |
| fn start_discovery(&mut self) -> bool; |
| |
| /// Cancels BREDR Inquiry. |
| fn cancel_discovery(&mut self) -> bool; |
| |
| /// Checks if discovery is started. |
| fn is_discovering(&self) -> bool; |
| |
| /// Checks when discovery ends in milliseconds from now. |
| fn get_discovery_end_millis(&self) -> u64; |
| |
| /// Initiates pairing to a remote device. Triggers connection if not already started. |
| fn create_bond(&mut self, device: BluetoothDevice, transport: BtTransport) -> BtStatus; |
| |
| /// Cancels any pending bond attempt on given device. |
| fn cancel_bond_process(&mut self, device: BluetoothDevice) -> bool; |
| |
| /// Removes pairing for given device. |
| fn remove_bond(&mut self, device: BluetoothDevice) -> bool; |
| |
| /// Returns a list of known bonded devices. |
| fn get_bonded_devices(&self) -> Vec<BluetoothDevice>; |
| |
| /// Gets the bond state of a single device. |
| fn get_bond_state(&self, device: BluetoothDevice) -> BtBondState; |
| |
| /// Set pin on bonding device. |
| fn set_pin(&self, device: BluetoothDevice, accept: bool, pin_code: Vec<u8>) -> bool; |
| |
| /// Set passkey on bonding device. |
| fn set_passkey(&self, device: BluetoothDevice, accept: bool, passkey: Vec<u8>) -> bool; |
| |
| /// Confirm that a pairing should be completed on a bonding device. |
| fn set_pairing_confirmation(&self, device: BluetoothDevice, accept: bool) -> bool; |
| |
| /// Gets the name of the remote device. |
| fn get_remote_name(&self, device: BluetoothDevice) -> String; |
| |
| /// Gets the type of the remote device. |
| fn get_remote_type(&self, device: BluetoothDevice) -> BtDeviceType; |
| |
| /// Gets the alias of the remote device. |
| fn get_remote_alias(&self, device: BluetoothDevice) -> String; |
| |
| /// Sets the alias of the remote device. |
| fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String); |
| |
| /// Gets the class of the remote device. |
| fn get_remote_class(&self, device: BluetoothDevice) -> u32; |
| |
| /// Gets the appearance of the remote device. |
| fn get_remote_appearance(&self, device: BluetoothDevice) -> u16; |
| |
| /// Gets whether the remote device is connected. |
| fn get_remote_connected(&self, device: BluetoothDevice) -> bool; |
| |
| /// Gets whether the remote device can wake the system. |
| fn get_remote_wake_allowed(&self, device: BluetoothDevice) -> bool; |
| |
| /// Gets the vendor and product information of the remote device. |
| fn get_remote_vendor_product_info(&self, device: BluetoothDevice) -> BtVendorProductInfo; |
| |
| /// Get the address type of the remote device. |
| fn get_remote_address_type(&self, device: BluetoothDevice) -> BtAddrType; |
| |
| /// Get the RSSI of the remote device. |
| fn get_remote_rssi(&self, device: BluetoothDevice) -> i8; |
| |
| /// Returns a list of connected devices. |
| fn get_connected_devices(&self) -> Vec<BluetoothDevice>; |
| |
| /// Gets the connection state of a single device. |
| fn get_connection_state(&self, device: BluetoothDevice) -> BtConnectionState; |
| |
| /// Gets the connection state of a specific profile. |
| fn get_profile_connection_state(&self, profile: Uuid) -> ProfileConnectionState; |
| |
| /// Returns the cached UUIDs of a remote device. |
| fn get_remote_uuids(&self, device: BluetoothDevice) -> Vec<Uuid>; |
| |
| /// Triggers SDP to get UUIDs of a remote device. |
| fn fetch_remote_uuids(&self, device: BluetoothDevice) -> bool; |
| |
| /// Triggers SDP and searches for a specific UUID on a remote device. |
| fn sdp_search(&self, device: BluetoothDevice, uuid: Uuid) -> bool; |
| |
| /// Creates a new SDP record. |
| fn create_sdp_record(&mut self, sdp_record: BtSdpRecord) -> bool; |
| |
| /// Removes the SDP record associated with the provided handle. |
| fn remove_sdp_record(&self, handle: i32) -> bool; |
| |
| /// Connect all profiles supported by device and enabled on adapter. |
| fn connect_all_enabled_profiles(&mut self, device: BluetoothDevice) -> BtStatus; |
| |
| /// Disconnect all profiles supported by device and enabled on adapter. |
| /// Note that it includes all custom profiles enabled by the users e.g. through SocketManager or |
| /// BluetoothGatt interfaces; The device shall be disconnected on baseband eventually. |
| fn disconnect_all_enabled_profiles(&mut self, device: BluetoothDevice) -> bool; |
| |
| /// Returns whether WBS is supported. |
| fn is_wbs_supported(&self) -> bool; |
| |
| /// Returns whether SWB is supported. |
| fn is_swb_supported(&self) -> bool; |
| |
| /// Returns a list of all the roles that are supported. |
| fn get_supported_roles(&self) -> Vec<BtAdapterRole>; |
| |
| /// Returns whether the coding format is supported. |
| fn is_coding_format_supported(&self, coding_format: EscoCodingFormat) -> bool; |
| |
| /// Returns whether LE Audio is supported. |
| fn is_le_audio_supported(&self) -> bool; |
| |
| /// Returns whether the remote device is a dual mode audio sink device (supports both classic and |
| /// LE Audio sink roles). |
| fn is_dual_mode_audio_sink_device(&self, device: BluetoothDevice) -> bool; |
| |
| /// Gets diagnostic output. |
| fn get_dumpsys(&self) -> String; |
| } |
| |
| /// Adapter API for Bluetooth qualification and verification. |
| /// |
| /// This interface is provided for testing and debugging. |
| /// Clients should not use this interface for production. |
| pub trait IBluetoothQALegacy { |
| /// Returns whether the adapter is connectable. |
| fn get_connectable(&self) -> bool; |
| |
| /// Sets connectability. Returns true on success, false otherwise. |
| fn set_connectable(&mut self, mode: bool) -> bool; |
| |
| /// Returns the adapter's Bluetooth friendly name. |
| fn get_alias(&self) -> String; |
| |
| /// Returns the adapter's Device ID information in modalias format |
| /// used by the kernel and udev. |
| fn get_modalias(&self) -> String; |
| |
| /// Gets HID report on the peer. |
| fn get_hid_report( |
| &mut self, |
| addr: RawAddress, |
| report_type: BthhReportType, |
| report_id: u8, |
| ) -> BtStatus; |
| |
| /// Sets HID report to the peer. |
| fn set_hid_report( |
| &mut self, |
| addr: RawAddress, |
| report_type: BthhReportType, |
| report: String, |
| ) -> BtStatus; |
| |
| /// Snd HID data report to the peer. |
| fn send_hid_data(&mut self, addr: RawAddress, data: String) -> BtStatus; |
| } |
| |
| /// Action events from lib.rs |
| pub enum AdapterActions { |
| /// Check whether the current set of found devices are still fresh. |
| DeviceFreshnessCheck, |
| |
| /// Connect to all supported profiles on target device. |
| ConnectAllProfiles(BluetoothDevice), |
| |
| /// Connect to the specified profiles on target device. |
| ConnectProfiles(Vec<Uuid>, BluetoothDevice), |
| |
| /// Scanner for BLE discovery is registered with given status and scanner id. |
| BleDiscoveryScannerRegistered(Uuid, u8, GattStatus), |
| |
| /// Scanner for BLE discovery is reporting a result. |
| BleDiscoveryScannerResult(ScanResult), |
| |
| /// Reset the discoverable mode to BtDiscMode::NonDiscoverable. |
| ResetDiscoverable, |
| |
| /// Create bond to the device stored in |pending_create_bond|. |
| CreateBond, |
| } |
| |
| /// Serializable device used in various apis. |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] |
| pub struct BluetoothDevice { |
| pub address: RawAddress, |
| pub name: String, |
| } |
| |
| impl BluetoothDevice { |
| pub(crate) fn new(address: RawAddress, name: String) -> Self { |
| Self { address, name } |
| } |
| |
| pub(crate) fn from_properties(in_properties: &Vec<BluetoothProperty>) -> Self { |
| let mut address = RawAddress::default(); |
| let mut name = String::from(""); |
| |
| for prop in in_properties { |
| match &prop { |
| BluetoothProperty::BdAddr(bdaddr) => { |
| address = *bdaddr; |
| } |
| BluetoothProperty::BdName(bdname) => { |
| name = bdname.clone(); |
| } |
| _ => {} |
| } |
| } |
| |
| Self { address, name } |
| } |
| } |
| |
| /// Internal data structure that keeps a map of cached properties for a remote device. |
| struct BluetoothDeviceContext { |
| /// Transport type reported by ACL connection (if completed). |
| pub acl_reported_transport: BtTransport, |
| |
| pub bredr_acl_state: BtAclState, |
| pub ble_acl_state: BtAclState, |
| pub bond_state: BtBondState, |
| pub info: BluetoothDevice, |
| pub last_seen: Instant, |
| pub properties: HashMap<BtPropertyType, BluetoothProperty>, |
| pub is_initiated_hh_connection: bool, |
| |
| /// If user wants to connect to all profiles, when new profiles are discovered we will also try |
| /// to connect them. |
| pub connect_to_new_profiles: bool, |
| } |
| |
| impl BluetoothDeviceContext { |
| pub(crate) fn new( |
| bond_state: BtBondState, |
| bredr_acl_state: BtAclState, |
| ble_acl_state: BtAclState, |
| info: BluetoothDevice, |
| last_seen: Instant, |
| properties: Vec<BluetoothProperty>, |
| ) -> BluetoothDeviceContext { |
| let mut device = BluetoothDeviceContext { |
| acl_reported_transport: BtTransport::Auto, |
| bredr_acl_state, |
| ble_acl_state, |
| bond_state, |
| info, |
| last_seen, |
| properties: HashMap::new(), |
| is_initiated_hh_connection: false, |
| connect_to_new_profiles: false, |
| }; |
| device.update_properties(&properties); |
| device |
| } |
| |
| pub(crate) fn update_properties(&mut self, in_properties: &Vec<BluetoothProperty>) { |
| for prop in in_properties { |
| // Handle merging of certain properties. |
| match &prop { |
| BluetoothProperty::BdAddr(bdaddr) => { |
| self.info.address = *bdaddr; |
| self.properties.insert(BtPropertyType::BdAddr, prop.clone()); |
| } |
| BluetoothProperty::BdName(bdname) => { |
| if !bdname.is_empty() { |
| self.info.name = bdname.clone(); |
| self.properties.insert(BtPropertyType::BdName, prop.clone()); |
| } |
| } |
| BluetoothProperty::Uuids(new_uuids) => { |
| // Merge the new and the old (if exist) UUIDs. |
| self.properties |
| .entry(BtPropertyType::Uuids) |
| .and_modify(|old_prop| { |
| if let BluetoothProperty::Uuids(old_uuids) = old_prop { |
| for uuid in new_uuids { |
| if !old_uuids.contains(uuid) { |
| old_uuids.push(*uuid); |
| } |
| } |
| } |
| }) |
| .or_insert(prop.clone()); |
| } |
| _ => { |
| self.properties.insert(prop.get_type(), prop.clone()); |
| } |
| } |
| } |
| } |
| |
| /// Mark this device as seen. |
| pub(crate) fn seen(&mut self) { |
| self.last_seen = Instant::now(); |
| } |
| |
| fn get_default_transport(&self) -> BtTransport { |
| self.properties.get(&BtPropertyType::TypeOfDevice).map_or(BtTransport::Auto, |prop| { |
| match prop { |
| BluetoothProperty::TypeOfDevice(t) => match *t { |
| BtDeviceType::Bredr => BtTransport::Bredr, |
| BtDeviceType::Ble => BtTransport::Le, |
| _ => BtTransport::Auto, |
| }, |
| _ => BtTransport::Auto, |
| } |
| }) |
| } |
| |
| /// Check if it is connected in at least one transport. |
| fn is_connected(&self) -> bool { |
| self.bredr_acl_state == BtAclState::Connected || self.ble_acl_state == BtAclState::Connected |
| } |
| |
| /// Set ACL state given transport. Return true if state changed. |
| fn set_transport_state(&mut self, transport: &BtTransport, state: &BtAclState) -> bool { |
| match (transport, self.get_default_transport()) { |
| (t, d) |
| if *t == BtTransport::Bredr |
| || (*t == BtTransport::Auto && d == BtTransport::Bredr) => |
| { |
| if self.bredr_acl_state == *state { |
| return false; |
| } |
| self.bredr_acl_state = state.clone(); |
| } |
| (t, d) |
| if *t == BtTransport::Le || (*t == BtTransport::Auto && d == BtTransport::Le) => |
| { |
| if self.ble_acl_state == *state { |
| return false; |
| } |
| self.ble_acl_state = state.clone(); |
| } |
| // both link transport and the default transport are Auto. |
| _ => { |
| warn!("Unable to decide the transport! Set current connection states bredr({:?}), ble({:?}) to {:?}", self.bredr_acl_state, self.ble_acl_state, *state); |
| if self.bredr_acl_state == *state && self.ble_acl_state == *state { |
| return false; |
| } |
| // There is no way for us to know which transport the link is referring to in this case. |
| self.ble_acl_state = state.clone(); |
| self.bredr_acl_state = state.clone(); |
| return true; |
| } |
| }; |
| true |
| } |
| } |
| |
| /// Structure to track all the signals for SIGTERM. |
| pub struct SigData { |
| pub enabled: Mutex<bool>, |
| pub enabled_notify: Condvar, |
| |
| pub thread_attached: Mutex<bool>, |
| pub thread_notify: Condvar, |
| |
| pub api_enabled: Mutex<bool>, |
| pub api_notify: Condvar, |
| } |
| |
| /// The interface for adapter callbacks registered through `IBluetooth::register_callback`. |
| pub trait IBluetoothCallback: RPCProxy { |
| /// When any adapter property changes. |
| fn on_adapter_property_changed(&mut self, prop: BtPropertyType); |
| |
| /// When any device properties change. |
| fn on_device_properties_changed( |
| &mut self, |
| remote_device: BluetoothDevice, |
| props: Vec<BtPropertyType>, |
| ); |
| |
| /// When any of the adapter local address is changed. |
| fn on_address_changed(&mut self, addr: RawAddress); |
| |
| /// When the adapter name is changed. |
| fn on_name_changed(&mut self, name: String); |
| |
| /// When the adapter's discoverable mode is changed. |
| fn on_discoverable_changed(&mut self, discoverable: bool); |
| |
| /// When a device is found via discovery. |
| fn on_device_found(&mut self, remote_device: BluetoothDevice); |
| |
| /// When a device is cleared from discovered devices cache. |
| fn on_device_cleared(&mut self, remote_device: BluetoothDevice); |
| |
| /// When a device is missing keys. |
| fn on_device_key_missing(&mut self, remote_device: BluetoothDevice); |
| |
| /// When the discovery state is changed. |
| fn on_discovering_changed(&mut self, discovering: bool); |
| |
| /// When there is a pairing/bonding process and requires agent to display the event to UI. |
| fn on_ssp_request( |
| &mut self, |
| remote_device: BluetoothDevice, |
| cod: u32, |
| variant: BtSspVariant, |
| passkey: u32, |
| ); |
| |
| /// When there is a pin request to display the event to client. |
| fn on_pin_request(&mut self, remote_device: BluetoothDevice, cod: u32, min_16_digit: bool); |
| |
| /// When there is a auto-gen pin to display the event to client. |
| fn on_pin_display(&mut self, remote_device: BluetoothDevice, pincode: String); |
| |
| /// When a bonding attempt has completed. |
| fn on_bond_state_changed(&mut self, status: u32, device_address: RawAddress, state: u32); |
| |
| /// When an SDP search has completed. |
| fn on_sdp_search_complete( |
| &mut self, |
| remote_device: BluetoothDevice, |
| searched_uuid: Uuid, |
| sdp_records: Vec<BtSdpRecord>, |
| ); |
| |
| /// When an SDP record has been successfully created. |
| fn on_sdp_record_created(&mut self, record: BtSdpRecord, handle: i32); |
| } |
| |
| pub trait IBluetoothConnectionCallback: RPCProxy { |
| /// Notification sent when a remote device completes HCI connection. |
| fn on_device_connected(&mut self, remote_device: BluetoothDevice); |
| |
| /// Notification sent when a remote device completes HCI disconnection. |
| fn on_device_disconnected(&mut self, remote_device: BluetoothDevice); |
| |
| /// Notification sent when a remote device fails to complete HCI connection. |
| fn on_device_connection_failed(&mut self, remote_device: BluetoothDevice, status: BtStatus); |
| } |
| |
| /// Implementation of the adapter API. |
| pub struct Bluetooth { |
| intf: Arc<Mutex<BluetoothInterface>>, |
| |
| virt_index: i32, |
| hci_index: i32, |
| remote_devices: HashMap<RawAddress, BluetoothDeviceContext>, |
| ble_scanner_id: Option<u8>, |
| ble_scanner_uuid: Option<Uuid>, |
| bluetooth_gatt: Option<Arc<Mutex<Box<BluetoothGatt>>>>, |
| bluetooth_media: Option<Arc<Mutex<Box<BluetoothMedia>>>>, |
| callbacks: Callbacks<dyn IBluetoothCallback + Send>, |
| connection_callbacks: Callbacks<dyn IBluetoothConnectionCallback + Send>, |
| discovering_started: Instant, |
| hh: Option<HidHost>, |
| is_connectable: bool, |
| is_socket_listening: bool, |
| discoverable_mode: BtDiscMode, |
| discoverable_duration: u32, |
| // This refers to the suspend mode of the functionality related to Classic scan mode, |
| // i.e., page scan and inquiry scan; Also known as connectable and discoverable. |
| scan_suspend_mode: SuspendMode, |
| is_discovering: bool, |
| is_discovering_before_suspend: bool, |
| is_discovery_paused: bool, |
| discovery_suspend_mode: SuspendMode, |
| local_address: Option<RawAddress>, |
| pending_discovery: bool, |
| properties: HashMap<BtPropertyType, BluetoothProperty>, |
| profiles_ready: bool, |
| freshness_check: Option<JoinHandle<()>>, |
| sdp: Option<Sdp>, |
| state: BtState, |
| disabling: bool, |
| tx: Sender<Message>, |
| api_tx: Sender<APIMessage>, |
| // Internal API members |
| discoverable_timeout: Option<JoinHandle<()>>, |
| cancelling_devices: HashSet<RawAddress>, |
| pending_create_bond: Option<(BluetoothDevice, BtTransport)>, |
| active_pairing_address: Option<RawAddress>, |
| le_supported_states: u64, |
| le_local_supported_features: u64, |
| |
| /// Used to notify signal handler that we have turned off the stack. |
| sig_notifier: Arc<SigData>, |
| |
| /// Virtual uhid device created to keep bluetooth as a wakeup source. |
| uhid_wakeup_source: UHid, |
| } |
| |
| impl Bluetooth { |
| /// Constructs the IBluetooth implementation. |
| pub fn new( |
| virt_index: i32, |
| hci_index: i32, |
| tx: Sender<Message>, |
| api_tx: Sender<APIMessage>, |
| sig_notifier: Arc<SigData>, |
| intf: Arc<Mutex<BluetoothInterface>>, |
| ) -> Bluetooth { |
| Bluetooth { |
| virt_index, |
| hci_index, |
| remote_devices: HashMap::new(), |
| callbacks: Callbacks::new(tx.clone(), Message::AdapterCallbackDisconnected), |
| connection_callbacks: Callbacks::new( |
| tx.clone(), |
| Message::ConnectionCallbackDisconnected, |
| ), |
| hh: None, |
| ble_scanner_id: None, |
| ble_scanner_uuid: None, |
| bluetooth_gatt: None, |
| bluetooth_media: None, |
| discovering_started: Instant::now(), |
| intf, |
| is_connectable: false, |
| is_socket_listening: false, |
| discoverable_mode: BtDiscMode::NonDiscoverable, |
| discoverable_duration: 0, |
| scan_suspend_mode: SuspendMode::Normal, |
| is_discovering: false, |
| is_discovering_before_suspend: false, |
| is_discovery_paused: false, |
| discovery_suspend_mode: SuspendMode::Normal, |
| local_address: None, |
| pending_discovery: false, |
| properties: HashMap::new(), |
| profiles_ready: false, |
| freshness_check: None, |
| sdp: None, |
| state: BtState::Off, |
| disabling: false, |
| tx, |
| api_tx, |
| // Internal API members |
| discoverable_timeout: None, |
| cancelling_devices: HashSet::new(), |
| pending_create_bond: None, |
| active_pairing_address: None, |
| le_supported_states: 0u64, |
| le_local_supported_features: 0u64, |
| sig_notifier, |
| uhid_wakeup_source: UHid::new(), |
| } |
| } |
| |
| pub(crate) fn set_media(&mut self, bluetooth_media: Arc<Mutex<Box<BluetoothMedia>>>) { |
| self.bluetooth_media = Some(bluetooth_media); |
| } |
| |
| pub(crate) fn set_gatt_and_init_scanner( |
| &mut self, |
| bluetooth_gatt: Arc<Mutex<Box<BluetoothGatt>>>, |
| ) { |
| self.bluetooth_gatt = Some(bluetooth_gatt.clone()); |
| |
| // Initialize the BLE scanner for discovery. |
| let callback_id = bluetooth_gatt |
| .lock() |
| .unwrap() |
| .register_scanner_callback(Box::new(BleDiscoveryCallbacks::new(self.tx.clone()))); |
| self.ble_scanner_uuid = Some(bluetooth_gatt.lock().unwrap().register_scanner(callback_id)); |
| } |
| |
| fn update_connectable_mode(&mut self) { |
| // Don't bother if we are disabling. See b/361510982 |
| if self.disabling { |
| return; |
| } |
| if self.get_scan_suspend_mode() != SuspendMode::Normal { |
| return; |
| } |
| // Set connectable if |
| // - there is bredr socket listening, or |
| // - there is a classic device bonded and not connected |
| self.set_connectable_internal( |
| self.is_socket_listening |
| || self.remote_devices.values().any(|ctx| { |
| ctx.bond_state == BtBondState::Bonded |
| && ctx.bredr_acl_state == BtAclState::Disconnected |
| && ctx |
| .properties |
| .get(&BtPropertyType::TypeOfDevice) |
| .and_then(|prop| match prop { |
| BluetoothProperty::TypeOfDevice(transport) => { |
| Some(*transport != BtDeviceType::Ble) |
| } |
| _ => None, |
| }) |
| .unwrap_or(false) |
| }), |
| ); |
| } |
| |
| pub(crate) fn set_socket_listening(&mut self, is_listening: bool) { |
| if self.is_socket_listening == is_listening { |
| return; |
| } |
| self.is_socket_listening = is_listening; |
| self.update_connectable_mode(); |
| } |
| |
| pub(crate) fn get_hci_index(&self) -> u16 { |
| self.hci_index as u16 |
| } |
| |
| pub(crate) fn handle_admin_policy_changed( |
| &mut self, |
| admin_policy_helper: BluetoothAdminPolicyHelper, |
| ) { |
| match ( |
| admin_policy_helper.is_profile_allowed(&Profile::Hid), |
| self.hh.as_ref().unwrap().is_hidp_activated, |
| ) { |
| (true, false) => self.hh.as_mut().unwrap().activate_hidp(true), |
| (false, true) => self.hh.as_mut().unwrap().activate_hidp(false), |
| _ => {} |
| } |
| |
| match ( |
| admin_policy_helper.is_profile_allowed(&Profile::Hogp), |
| self.hh.as_ref().unwrap().is_hogp_activated, |
| ) { |
| (true, false) => self.hh.as_mut().unwrap().activate_hogp(true), |
| (false, true) => self.hh.as_mut().unwrap().activate_hogp(false), |
| _ => {} |
| } |
| |
| if self.hh.as_mut().unwrap().configure_enabled_profiles() { |
| self.hh.as_mut().unwrap().disable(); |
| let tx = self.tx.clone(); |
| |
| tokio::spawn(async move { |
| // Wait 100 milliseconds to prevent race condition caused by quick disable then |
| // enable. |
| // TODO: (b/272191117): don't enable until we're sure disable is done. |
| tokio::time::sleep(Duration::from_millis(100)).await; |
| let _ = tx.send(Message::HidHostEnable).await; |
| }); |
| } |
| } |
| |
| pub fn enable_hidhost(&mut self) { |
| self.hh.as_mut().unwrap().enable(); |
| } |
| |
| pub fn init_profiles(&mut self) { |
| self.sdp = Some(Sdp::new(&self.intf.lock().unwrap())); |
| self.sdp.as_mut().unwrap().initialize(SdpCallbacksDispatcher { |
| dispatch: make_message_dispatcher(self.tx.clone(), Message::Sdp), |
| }); |
| |
| self.hh = Some(HidHost::new(&self.intf.lock().unwrap())); |
| self.hh.as_mut().unwrap().initialize(HHCallbacksDispatcher { |
| dispatch: make_message_dispatcher(self.tx.clone(), Message::HidHost), |
| }); |
| |
| // Mark profiles as ready |
| self.profiles_ready = true; |
| } |
| |
| fn update_local_address(&mut self, addr: RawAddress) { |
| self.local_address = Some(addr); |
| |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_address_changed(addr); |
| }); |
| } |
| |
| pub(crate) fn adapter_callback_disconnected(&mut self, id: u32) { |
| self.callbacks.remove_callback(id); |
| } |
| |
| pub(crate) fn connection_callback_disconnected(&mut self, id: u32) { |
| self.connection_callbacks.remove_callback(id); |
| } |
| |
| pub fn shutdown_adapter(&mut self, abort: bool) -> bool { |
| self.disabling = true; |
| |
| if !abort { |
| if !self.set_discoverable(BtDiscMode::NonDiscoverable, 0) { |
| warn!("set_discoverable failed on disabling"); |
| } |
| if !self.set_connectable_internal(false) { |
| warn!("set_connectable_internal failed on disabling"); |
| } |
| } |
| |
| self.intf.lock().unwrap().disable() == 0 |
| } |
| |
| fn get_remote_device_property( |
| &self, |
| device: &BluetoothDevice, |
| property_type: &BtPropertyType, |
| ) -> Option<BluetoothProperty> { |
| self.remote_devices |
| .get(&device.address) |
| .and_then(|d| d.properties.get(property_type)) |
| .cloned() |
| } |
| |
| fn set_remote_device_property( |
| &mut self, |
| device: &BluetoothDevice, |
| property_type: BtPropertyType, |
| property: BluetoothProperty, |
| ) -> Result<(), ()> { |
| let Some(remote_device) = self.remote_devices.get_mut(&device.address) else { |
| return Err(()); |
| }; |
| |
| // TODO: Determine why a callback isn't invoked to do this. |
| remote_device.properties.insert(property_type, property.clone()); |
| self.intf.lock().unwrap().set_remote_device_property(&mut device.address.clone(), property); |
| Ok(()) |
| } |
| |
| /// Returns whether the adapter is connectable. |
| pub(crate) fn get_connectable_internal(&self) -> bool { |
| self.discoverable_mode != BtDiscMode::NonDiscoverable || self.is_connectable |
| } |
| |
| /// Sets the adapter's connectable mode for classic connections. |
| pub(crate) fn set_connectable_internal(&mut self, mode: bool) -> bool { |
| if self.get_scan_suspend_mode() != SuspendMode::Normal { |
| // We will always trigger an update on resume so no need so store the mode change. |
| return false; |
| } |
| if self.is_connectable == mode { |
| return true; |
| } |
| if self.discoverable_mode != BtDiscMode::NonDiscoverable { |
| // Discoverable always implies connectable. Don't affect the discoverable mode for now |
| // and the connectable mode would be restored when discoverable becomes off. |
| self.is_connectable = mode; |
| return true; |
| } |
| self.intf.lock().unwrap().set_scan_mode(if mode { |
| BtScanMode::Connectable |
| } else { |
| BtScanMode::None_ |
| }); |
| self.is_connectable = mode; |
| true |
| } |
| |
| /// Returns adapter's discoverable mode. |
| pub(crate) fn get_discoverable_mode_internal(&self) -> BtDiscMode { |
| self.discoverable_mode.clone() |
| } |
| |
| /// Set the suspend mode for scan mode (connectable/discoverable mode). |
| pub(crate) fn set_scan_suspend_mode(&mut self, suspend_mode: SuspendMode) { |
| if suspend_mode != self.scan_suspend_mode { |
| self.scan_suspend_mode = suspend_mode; |
| } |
| } |
| |
| /// Gets current suspend mode for scan mode (connectable/discoverable mode). |
| pub(crate) fn get_scan_suspend_mode(&self) -> SuspendMode { |
| self.scan_suspend_mode.clone() |
| } |
| |
| /// Enters the suspend mode for scan mode (connectable/discoverable mode). |
| pub(crate) fn scan_mode_enter_suspend(&mut self) -> BtStatus { |
| if self.get_scan_suspend_mode() != SuspendMode::Normal { |
| return BtStatus::Busy; |
| } |
| self.set_scan_suspend_mode(SuspendMode::Suspending); |
| |
| self.intf.lock().unwrap().set_scan_mode(BtScanMode::None_); |
| |
| self.set_scan_suspend_mode(SuspendMode::Suspended); |
| |
| BtStatus::Success |
| } |
| |
| /// Exits the suspend mode for scan mode (connectable/discoverable mode). |
| pub(crate) fn scan_mode_exit_suspend(&mut self) -> BtStatus { |
| if self.get_scan_suspend_mode() != SuspendMode::Suspended { |
| return BtStatus::Busy; |
| } |
| self.set_scan_suspend_mode(SuspendMode::Resuming); |
| |
| let mode = match self.discoverable_mode { |
| BtDiscMode::LimitedDiscoverable => BtScanMode::ConnectableLimitedDiscoverable, |
| BtDiscMode::GeneralDiscoverable => BtScanMode::ConnectableDiscoverable, |
| BtDiscMode::NonDiscoverable => match self.is_connectable { |
| true => BtScanMode::Connectable, |
| false => BtScanMode::None_, |
| }, |
| }; |
| self.intf.lock().unwrap().set_scan_mode(mode); |
| |
| self.set_scan_suspend_mode(SuspendMode::Normal); |
| |
| // Update is only available after SuspendMode::Normal |
| self.update_connectable_mode(); |
| |
| BtStatus::Success |
| } |
| |
| /// Returns adapter's alias. |
| pub(crate) fn get_alias_internal(&self) -> String { |
| let name = self.get_name(); |
| if !name.is_empty() { |
| return name; |
| } |
| |
| // If the adapter name is empty, generate one based on local BDADDR |
| // so that test programs can have a friendly name for the adapter. |
| match self.local_address { |
| None => "floss_0000".to_string(), |
| Some(addr) => format!("floss_{:02X}{:02X}", addr.address[4], addr.address[5]), |
| } |
| } |
| |
| // TODO(b/328675014): Add BtAddrType and BtTransport parameters |
| pub(crate) fn get_hid_report_internal( |
| &mut self, |
| mut addr: RawAddress, |
| report_type: BthhReportType, |
| report_id: u8, |
| ) -> BtStatus { |
| self.hh.as_mut().unwrap().get_report( |
| &mut addr, |
| BtAddrType::Public, |
| BtTransport::Auto, |
| report_type, |
| report_id, |
| 128, |
| ) |
| } |
| |
| // TODO(b/328675014): Add BtAddrType and BtTransport parameters |
| pub(crate) fn set_hid_report_internal( |
| &mut self, |
| mut addr: RawAddress, |
| report_type: BthhReportType, |
| report: String, |
| ) -> BtStatus { |
| let mut rb = report.clone().into_bytes(); |
| self.hh.as_mut().unwrap().set_report( |
| &mut addr, |
| BtAddrType::Public, |
| BtTransport::Auto, |
| report_type, |
| rb.as_mut_slice(), |
| ) |
| } |
| |
| // TODO(b/328675014): Add BtAddrType and BtTransport parameters |
| pub(crate) fn send_hid_data_internal( |
| &mut self, |
| mut addr: RawAddress, |
| data: String, |
| ) -> BtStatus { |
| let mut rb = data.clone().into_bytes(); |
| self.hh.as_mut().unwrap().send_data( |
| &mut addr, |
| BtAddrType::Public, |
| BtTransport::Auto, |
| rb.as_mut_slice(), |
| ) |
| } |
| |
| // TODO(b/328675014): Add BtAddrType and BtTransport parameters |
| pub(crate) fn send_hid_virtual_unplug_internal(&mut self, mut addr: RawAddress) -> BtStatus { |
| self.hh.as_mut().unwrap().virtual_unplug(&mut addr, BtAddrType::Public, BtTransport::Auto) |
| } |
| |
| /// Returns all bonded and connected devices. |
| pub(crate) fn get_bonded_and_connected_devices(&mut self) -> Vec<BluetoothDevice> { |
| self.remote_devices |
| .values() |
| .filter(|v| v.is_connected() && v.bond_state == BtBondState::Bonded) |
| .map(|v| v.info.clone()) |
| .collect() |
| } |
| |
| /// Returns all devices with UUIDs, while None means there's not yet an UUID property change. |
| pub(crate) fn get_all_devices_and_uuids(&self) -> Vec<(BluetoothDevice, Option<Vec<Uuid>>)> { |
| self.remote_devices |
| .values() |
| .map(|d| { |
| let uuids = d.properties.get(&BtPropertyType::Uuids).and_then(|prop| match prop { |
| BluetoothProperty::Uuids(uuids) => Some(uuids.clone()), |
| _ => None, |
| }); |
| (d.info.clone(), uuids) |
| }) |
| .collect() |
| } |
| |
| /// Gets the bond state of a single device with its address. |
| pub fn get_bond_state_by_addr(&self, addr: &RawAddress) -> BtBondState { |
| self.remote_devices.get(addr).map_or(BtBondState::NotBonded, |d| d.bond_state.clone()) |
| } |
| |
| /// Gets whether a single device is connected with its address. |
| fn get_acl_state_by_addr(&self, addr: &RawAddress) -> bool { |
| self.remote_devices.get(addr).map_or(false, |d| d.is_connected()) |
| } |
| |
| /// Check whether remote 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) { |
| // A remote device is considered fresh if: |
| // * It was last seen less than |FOUND_DEVICE_FRESHNESS| ago. |
| // * It is bonded / bonding (i.e., not NotBonded) |
| // * 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.is_connected() || d.bond_state != BtBondState::NotBonded |
| } |
| |
| let now = Instant::now(); |
| let stale_devices: Vec<BluetoothDevice> = self |
| .remote_devices |
| .values() |
| .filter(|d| !is_fresh(d, &now)) |
| .map(|d| d.info.clone()) |
| .collect(); |
| |
| // Retain only devices that are fresh. |
| self.remote_devices.retain(|_, d| is_fresh(d, &now)); |
| |
| for d in stale_devices { |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_device_cleared(d.clone()); |
| }); |
| } |
| } |
| |
| fn send_metrics_remote_device_info(device: &BluetoothDeviceContext) { |
| if device.bond_state != BtBondState::Bonded && !device.is_connected() { |
| return; |
| } |
| |
| let mut class_of_device = 0u32; |
| let mut device_type = BtDeviceType::Unknown; |
| let mut appearance = 0u16; |
| let mut vpi = |
| BtVendorProductInfo { vendor_id_src: 0, vendor_id: 0, product_id: 0, version: 0 }; |
| |
| for prop in device.properties.values() { |
| match prop { |
| BluetoothProperty::TypeOfDevice(p) => device_type = p.clone(), |
| BluetoothProperty::ClassOfDevice(p) => class_of_device = *p, |
| BluetoothProperty::Appearance(p) => appearance = *p, |
| BluetoothProperty::VendorProductInfo(p) => vpi = *p, |
| _ => (), |
| } |
| } |
| |
| metrics::device_info_report( |
| device.info.address, |
| device_type, |
| class_of_device, |
| appearance, |
| vpi.vendor_id, |
| vpi.vendor_id_src, |
| vpi.product_id, |
| vpi.version, |
| ); |
| } |
| |
| /// Handle adapter actions. |
| pub(crate) fn handle_actions(&mut self, action: AdapterActions) { |
| match action { |
| AdapterActions::DeviceFreshnessCheck => { |
| self.trigger_freshness_check(); |
| } |
| |
| AdapterActions::ConnectAllProfiles(device) => { |
| self.connect_all_enabled_profiles(device); |
| } |
| |
| AdapterActions::ConnectProfiles(uuids, device) => { |
| self.connect_profiles_internal(&uuids, device); |
| } |
| |
| AdapterActions::BleDiscoveryScannerRegistered(uuid, scanner_id, status) => { |
| if let Some(app_uuid) = self.ble_scanner_uuid { |
| if app_uuid == uuid { |
| if status == GattStatus::Success { |
| self.ble_scanner_id = Some(scanner_id); |
| } else { |
| log::error!("BLE discovery scanner failed to register: {:?}", status); |
| } |
| } |
| } |
| } |
| |
| AdapterActions::BleDiscoveryScannerResult(result) => { |
| // Generate a vector of properties from ScanResult. |
| let properties = { |
| let mut props = vec![]; |
| props.push(BluetoothProperty::BdName(result.name.clone())); |
| props.push(BluetoothProperty::BdAddr(result.address)); |
| if !result.service_uuids.is_empty() { |
| props.push(BluetoothProperty::Uuids(result.service_uuids.clone())); |
| } |
| if !result.service_data.is_empty() { |
| props.push(BluetoothProperty::Uuids( |
| result |
| .service_data |
| .keys() |
| .map(|v| Uuid::from_string(v).unwrap()) |
| .collect(), |
| )); |
| } |
| props.push(BluetoothProperty::RemoteRssi(result.rssi)); |
| props.push(BluetoothProperty::RemoteAddrType((result.addr_type as u32).into())); |
| props |
| }; |
| |
| let device_info = BluetoothDevice::from_properties(&properties); |
| self.check_new_property_and_potentially_connect_profiles( |
| result.address, |
| &properties, |
| ); |
| |
| self.remote_devices |
| .entry(device_info.address) |
| .and_modify(|d| { |
| d.update_properties(&properties); |
| d.seen(); |
| }) |
| .or_insert(BluetoothDeviceContext::new( |
| BtBondState::NotBonded, |
| BtAclState::Disconnected, |
| BtAclState::Disconnected, |
| device_info, |
| Instant::now(), |
| properties, |
| )); |
| } |
| |
| AdapterActions::ResetDiscoverable => { |
| self.set_discoverable(BtDiscMode::NonDiscoverable, 0); |
| } |
| |
| AdapterActions::CreateBond => { |
| if let Some((device, transport)) = self.pending_create_bond.take() { |
| let status = self.create_bond(device, transport); |
| if status != BtStatus::Success { |
| error!("Failed CreateBond status={:?}", status); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Creates a file to notify btmanagerd the adapter is enabled. |
| fn create_pid_file(&self) -> std::io::Result<()> { |
| let file_name = format!("{}/bluetooth{}.pid", PID_DIR, self.virt_index); |
| let mut f = File::create(file_name)?; |
| f.write_all(process::id().to_string().as_bytes())?; |
| Ok(()) |
| } |
| |
| /// Removes the file to notify btmanagerd the adapter is disabled. |
| fn remove_pid_file(&self) -> std::io::Result<()> { |
| let file_name = format!("{}/bluetooth{}.pid", PID_DIR, self.virt_index); |
| std::fs::remove_file(file_name)?; |
| Ok(()) |
| } |
| |
| /// Set the suspend mode. |
| pub fn set_discovery_suspend_mode(&mut self, suspend_mode: SuspendMode) { |
| if suspend_mode != self.discovery_suspend_mode { |
| self.discovery_suspend_mode = suspend_mode; |
| } |
| } |
| |
| /// Gets current suspend mode. |
| pub fn get_discovery_suspend_mode(&self) -> SuspendMode { |
| self.discovery_suspend_mode.clone() |
| } |
| |
| /// Enters the suspend mode for discovery. |
| pub fn discovery_enter_suspend(&mut self) -> BtStatus { |
| if self.get_discovery_suspend_mode() != SuspendMode::Normal { |
| return BtStatus::Busy; |
| } |
| self.set_discovery_suspend_mode(SuspendMode::Suspending); |
| |
| if self.is_discovering { |
| self.is_discovering_before_suspend = true; |
| self.cancel_discovery(); |
| } |
| self.set_discovery_suspend_mode(SuspendMode::Suspended); |
| |
| BtStatus::Success |
| } |
| |
| /// Exits the suspend mode for discovery. |
| pub fn discovery_exit_suspend(&mut self) -> BtStatus { |
| if self.get_discovery_suspend_mode() != SuspendMode::Suspended { |
| return BtStatus::Busy; |
| } |
| self.set_discovery_suspend_mode(SuspendMode::Resuming); |
| |
| if self.is_discovering_before_suspend { |
| self.is_discovering_before_suspend = false; |
| self.start_discovery(); |
| } |
| self.set_discovery_suspend_mode(SuspendMode::Normal); |
| |
| BtStatus::Success |
| } |
| |
| /// Temporarily stop the discovery process and mark it as paused so that clients cannot restart |
| /// it. |
| fn pause_discovery(&mut self) { |
| self.cancel_discovery(); |
| self.is_discovery_paused = true; |
| } |
| |
| /// Remove the paused flag to allow clients to begin discovery, and if there is already a |
| /// pending request, start discovery. |
| fn resume_discovery(&mut self) { |
| self.is_discovery_paused = false; |
| if self.pending_discovery { |
| self.pending_discovery = false; |
| self.start_discovery(); |
| } |
| } |
| |
| /// Return if there are wake-allowed device in bonded status. |
| fn get_wake_allowed_device_bonded(&self) -> bool { |
| self.get_bonded_devices().into_iter().any(|d| self.get_remote_wake_allowed(d)) |
| } |
| |
| /// Powerd recognizes bluetooth activities as valid wakeup sources if powerd keeps bluetooth in |
| /// the monitored path. This only happens if there is at least one valid wake-allowed BT device |
| /// connected during the suspending process. If there is no BT devices connected at any time |
| /// during the suspending process, the wakeup count will be lost, and system goes to dark |
| /// resume instead of full resume. |
| /// Bluetooth stack disconnects all physical bluetooth HID devices for suspend, so a virtual |
| /// uhid device is necessary to keep bluetooth as a valid wakeup source. |
| fn create_uhid_for_suspend_wakesource(&mut self) { |
| if !self.uhid_wakeup_source.is_empty() { |
| return; |
| } |
| match self.uhid_wakeup_source.create( |
| "VIRTUAL_SUSPEND_UHID".to_string(), |
| self.get_address(), |
| RawAddress::empty(), |
| ) { |
| Err(e) => error!("Fail to create uhid {}", e), |
| Ok(_) => (), |
| } |
| } |
| |
| /// Clear the UHID device. |
| fn clear_uhid(&mut self) { |
| self.uhid_wakeup_source.clear(); |
| } |
| |
| /// Checks whether pairing is busy. |
| pub fn is_pairing_busy(&self) -> bool { |
| self.intf.lock().unwrap().pairing_is_busy() |
| || self.active_pairing_address.is_some() |
| || self.pending_create_bond.is_some() |
| } |
| |
| /// Checks whether a Hid/Hog connection is being established or active. |
| pub fn is_initiated_hh_connection(&self, device_address: &RawAddress) -> bool { |
| self.remote_devices |
| .get(&device_address) |
| .map_or(false, |context| context.is_initiated_hh_connection) |
| } |
| |
| /// Checks whether the list of device properties contains some UUID we should connect now |
| /// This function also connects those UUIDs. |
| fn check_new_property_and_potentially_connect_profiles( |
| &self, |
| addr: RawAddress, |
| properties: &Vec<BluetoothProperty>, |
| ) { |
| // Return early if no need to connect new profiles |
| if !self.remote_devices.get(&addr).map_or(false, |d| d.connect_to_new_profiles) { |
| return; |
| } |
| |
| // Get the reported UUIDs, if any. Otherwise return early. |
| let mut new_uuids: Vec<Uuid> = vec![]; |
| for prop in properties.iter() { |
| if let BluetoothProperty::Uuids(value) = prop { |
| new_uuids.extend(value); |
| } |
| } |
| if new_uuids.is_empty() { |
| return; |
| } |
| |
| // Only connect if the UUID is not seen before and it's supported |
| let device = BluetoothDevice::new(addr, "".to_string()); |
| let current_uuids = self.get_remote_uuids(device.clone()); |
| new_uuids.retain(|uuid| !current_uuids.contains(uuid)); |
| |
| let profile_known_and_supported = new_uuids.iter().any(|uuid| { |
| if let Some(profile) = UuidHelper::is_known_profile(uuid) { |
| return UuidHelper::is_profile_supported(&profile); |
| } |
| return false; |
| }); |
| if !profile_known_and_supported { |
| return; |
| } |
| |
| log::info!("[{}]: Connecting to newly discovered profiles", DisplayAddress(&addr)); |
| let tx = self.tx.clone(); |
| tokio::spawn(async move { |
| let _ = tx |
| .send(Message::AdapterActions(AdapterActions::ConnectProfiles(new_uuids, device))) |
| .await; |
| }); |
| } |
| |
| /// Connect these profiles of a peripheral device |
| fn connect_profiles_internal(&mut self, uuids: &Vec<Uuid>, device: BluetoothDevice) { |
| let addr = device.address; |
| if !self.get_acl_state_by_addr(&addr) { |
| // log ACL connection attempt if it's not already connected. |
| metrics::acl_connect_attempt(addr, BtAclState::Connected); |
| // Pause discovery before connecting, or the ACL connection request may conflict with |
| // the ongoing inquiry. |
| self.pause_discovery(); |
| } |
| |
| let mut has_supported_profile = false; |
| let mut has_le_media_profile = false; |
| let mut has_classic_media_profile = false; |
| |
| for uuid in uuids.iter() { |
| match UuidHelper::is_known_profile(uuid) { |
| Some(p) => { |
| if UuidHelper::is_profile_supported(&p) { |
| match p { |
| Profile::Hid | Profile::Hogp => { |
| has_supported_profile = true; |
| // TODO(b/328675014): Use BtAddrType |
| // and BtTransport from |
| // BluetoothDevice instead of default |
| let status = self.hh.as_ref().unwrap().connect( |
| &mut addr.clone(), |
| BtAddrType::Public, |
| BtTransport::Auto, |
| ); |
| metrics::profile_connection_state_changed( |
| addr, |
| p as u32, |
| BtStatus::Success, |
| BthhConnectionState::Connecting as u32, |
| ); |
| |
| if status != BtStatus::Success { |
| metrics::profile_connection_state_changed( |
| addr, |
| p as u32, |
| status, |
| BthhConnectionState::Disconnected as u32, |
| ); |
| } |
| } |
| |
| // TODO(b/317682584): implement policy to connect to LEA, VC, and CSIS |
| Profile::LeAudio | Profile::VolumeControl | Profile::CoordinatedSet |
| if !has_le_media_profile => |
| { |
| has_le_media_profile = true; |
| let txl = self.tx.clone(); |
| topstack::get_runtime().spawn(async move { |
| let _ = txl |
| .send(Message::Media( |
| MediaActions::ConnectLeaGroupByMemberAddress(addr), |
| )) |
| .await; |
| }); |
| } |
| |
| Profile::A2dpSink | Profile::A2dpSource | Profile::Hfp |
| if !has_classic_media_profile => |
| { |
| has_supported_profile = true; |
| has_classic_media_profile = true; |
| let txl = self.tx.clone(); |
| topstack::get_runtime().spawn(async move { |
| let _ = |
| txl.send(Message::Media(MediaActions::Connect(addr))).await; |
| }); |
| } |
| |
| // We don't connect most profiles |
| _ => (), |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| |
| // If the device does not have a profile that we are interested in connecting to, resume |
| // discovery now. Other cases will be handled in the ACL connection state or bond state |
| // callbacks. |
| if !has_supported_profile { |
| self.resume_discovery(); |
| } |
| } |
| |
| fn fire_device_connection_or_bonded_state_changed(&self, addr: RawAddress) { |
| if let Some(device) = self.remote_devices.get(&addr) { |
| let tx = self.tx.clone(); |
| let bredr_acl_state = device.bredr_acl_state.clone(); |
| let ble_acl_state = device.ble_acl_state.clone(); |
| let bond_state = device.bond_state.clone(); |
| let transport = match self.get_remote_type(device.info.clone()) { |
| BtDeviceType::Bredr => BtTransport::Bredr, |
| BtDeviceType::Ble => BtTransport::Le, |
| _ => device.acl_reported_transport.clone(), |
| }; |
| tokio::spawn(async move { |
| let _ = tx |
| .send(Message::OnDeviceConnectionOrBondStateChanged( |
| addr, |
| bredr_acl_state, |
| ble_acl_state, |
| bond_state, |
| transport, |
| )) |
| .await; |
| }); |
| } |
| } |
| } |
| |
| #[btif_callbacks_dispatcher(dispatch_base_callbacks, BaseCallbacks)] |
| #[allow(unused_variables)] |
| pub(crate) trait BtifBluetoothCallbacks { |
| #[btif_callback(AdapterState)] |
| fn adapter_state_changed(&mut self, state: BtState) {} |
| |
| #[btif_callback(AdapterProperties)] |
| fn adapter_properties_changed( |
| &mut self, |
| status: BtStatus, |
| num_properties: i32, |
| properties: Vec<BluetoothProperty>, |
| ) { |
| } |
| |
| #[btif_callback(DeviceFound)] |
| fn device_found(&mut self, n: i32, properties: Vec<BluetoothProperty>) {} |
| |
| #[btif_callback(DiscoveryState)] |
| fn discovery_state(&mut self, state: BtDiscoveryState) {} |
| |
| #[btif_callback(SspRequest)] |
| fn ssp_request(&mut self, remote_addr: RawAddress, variant: BtSspVariant, passkey: u32) {} |
| |
| #[btif_callback(BondState)] |
| fn bond_state( |
| &mut self, |
| status: BtStatus, |
| addr: RawAddress, |
| bond_state: BtBondState, |
| fail_reason: i32, |
| ) { |
| } |
| |
| #[btif_callback(RemoteDeviceProperties)] |
| fn remote_device_properties_changed( |
| &mut self, |
| status: BtStatus, |
| addr: RawAddress, |
| addr_type: u8, |
| num_properties: i32, |
| properties: Vec<BluetoothProperty>, |
| ) { |
| } |
| |
| #[btif_callback(AclState)] |
| fn acl_state( |
| &mut self, |
| status: BtStatus, |
| addr: RawAddress, |
| state: BtAclState, |
| link_type: BtTransport, |
| hci_reason: BtHciErrorCode, |
| conn_direction: BtConnectionDirection, |
| acl_handle: u16, |
| ) { |
| } |
| |
| #[btif_callback(LeRandCallback)] |
| fn le_rand_cb(&mut self, random: u64) {} |
| |
| #[btif_callback(PinRequest)] |
| fn pin_request( |
| &mut self, |
| remote_addr: RawAddress, |
| remote_name: String, |
| cod: u32, |
| min_16_digit: bool, |
| ) { |
| } |
| |
| #[btif_callback(ThreadEvent)] |
| fn thread_event(&mut self, event: BtThreadEvent) {} |
| |
| #[btif_callback(KeyMissing)] |
| fn key_missing(&mut self, addr: RawAddress) {} |
| } |
| |
| #[btif_callbacks_dispatcher(dispatch_hid_host_callbacks, HHCallbacks)] |
| pub(crate) trait BtifHHCallbacks { |
| #[btif_callback(ConnectionState)] |
| fn connection_state( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| state: BthhConnectionState, |
| ); |
| |
| #[btif_callback(HidInfo)] |
| fn hid_info( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| info: BthhHidInfo, |
| ); |
| |
| #[btif_callback(ProtocolMode)] |
| fn protocol_mode( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| mode: BthhProtocolMode, |
| ); |
| |
| #[btif_callback(IdleTime)] |
| fn idle_time( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| idle_rate: i32, |
| ); |
| |
| #[btif_callback(GetReport)] |
| fn get_report( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| data: Vec<u8>, |
| size: i32, |
| ); |
| |
| #[btif_callback(Handshake)] |
| fn handshake( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| ); |
| } |
| |
| #[btif_callbacks_dispatcher(dispatch_sdp_callbacks, SdpCallbacks)] |
| pub(crate) trait BtifSdpCallbacks { |
| #[btif_callback(SdpSearch)] |
| fn sdp_search( |
| &mut self, |
| status: BtStatus, |
| address: RawAddress, |
| uuid: Uuid, |
| count: i32, |
| records: Vec<BtSdpRecord>, |
| ); |
| } |
| |
| pub fn get_bt_dispatcher(tx: Sender<Message>) -> BaseCallbacksDispatcher { |
| BaseCallbacksDispatcher { dispatch: make_message_dispatcher(tx, Message::Base) } |
| } |
| |
| impl BtifBluetoothCallbacks for Bluetooth { |
| #[log_cb_args] |
| fn adapter_state_changed(&mut self, state: BtState) { |
| let prev_state = self.state.clone(); |
| self.state = state; |
| metrics::adapter_state_changed(self.state.clone()); |
| |
| // If it's the same state as before, no further action |
| if self.state == prev_state { |
| return; |
| } |
| |
| match self.state { |
| BtState::Off => { |
| self.properties.clear(); |
| match self.remove_pid_file() { |
| Err(err) => warn!("remove_pid_file() error: {}", err), |
| _ => (), |
| } |
| |
| self.clear_uhid(); |
| |
| // Let the signal notifier know we are turned off. |
| *self.sig_notifier.enabled.lock().unwrap() = false; |
| self.sig_notifier.enabled_notify.notify_all(); |
| } |
| |
| BtState::On => { |
| // Initialize core profiles |
| self.init_profiles(); |
| |
| // Trigger properties update |
| self.intf.lock().unwrap().get_adapter_properties(); |
| |
| // Also need to manually request some properties |
| self.intf.lock().unwrap().get_adapter_property(BtPropertyType::ClassOfDevice); |
| let mut controller = controller::Controller::new(); |
| self.le_supported_states = controller.get_ble_supported_states(); |
| self.le_local_supported_features = controller.get_ble_local_supported_features(); |
| |
| // Update connectable mode so that disconnected bonded classic device can reconnect |
| self.update_connectable_mode(); |
| |
| // Spawn a freshness check job in the background. |
| if let Some(h) = self.freshness_check.take() { |
| h.abort() |
| } |
| let txl = self.tx.clone(); |
| self.freshness_check = Some(tokio::spawn(async move { |
| loop { |
| time::sleep(FOUND_DEVICE_FRESHNESS).await; |
| let _ = txl |
| .send(Message::AdapterActions(AdapterActions::DeviceFreshnessCheck)) |
| .await; |
| } |
| })); |
| |
| if self.get_wake_allowed_device_bonded() { |
| self.create_uhid_for_suspend_wakesource(); |
| } |
| // Notify the signal notifier that we are turned on. |
| *self.sig_notifier.enabled.lock().unwrap() = true; |
| self.sig_notifier.enabled_notify.notify_all(); |
| |
| // Signal that the stack is up and running. |
| match self.create_pid_file() { |
| Err(err) => warn!("create_pid_file() error: {}", err), |
| _ => (), |
| } |
| |
| // Inform the rest of the stack we're ready. |
| let txl = self.tx.clone(); |
| let api_txl = self.api_tx.clone(); |
| tokio::spawn(async move { |
| let _ = txl.send(Message::AdapterReady).await; |
| }); |
| tokio::spawn(async move { |
| let _ = api_txl.send(APIMessage::IsReady(BluetoothAPI::Adapter)).await; |
| }); |
| // Log LL privacy states. |
| // LL privacy is set according to feature flag when initializing chrome. When the |
| // sysprop override file is updated, the adapter is powered off/on to read the new |
| // settings. As a result, log the LL privacy state at adapter on. |
| // Multiple users may have different LL privacy settings, and the user setting is |
| // applied at user login. |
| let llp_state = |
| u32::from(sysprop::get_bool(sysprop::PropertyBool::LePrivacyEnabled)); |
| let rpa_state = u32::from(sysprop::get_bool( |
| sysprop::PropertyBool::LePrivacyOwnAddressTypeEnabled, |
| )); |
| metrics::ll_privacy_state(llp_state, rpa_state); |
| } |
| } |
| } |
| |
| #[allow(unused_variables)] |
| #[log_cb_args] |
| fn adapter_properties_changed( |
| &mut self, |
| status: BtStatus, |
| num_properties: i32, |
| properties: Vec<BluetoothProperty>, |
| ) { |
| if status != BtStatus::Success { |
| return; |
| } |
| |
| // Update local property cache |
| for prop in properties { |
| self.properties.insert(prop.get_type(), prop.clone()); |
| |
| match &prop { |
| BluetoothProperty::BdAddr(bdaddr) => { |
| self.update_local_address(*bdaddr); |
| } |
| BluetoothProperty::AdapterBondedDevices(bondlist) => { |
| for addr in bondlist.iter() { |
| self.remote_devices |
| .entry(*addr) |
| .and_modify(|d| d.bond_state = BtBondState::Bonded) |
| .or_insert(BluetoothDeviceContext::new( |
| BtBondState::Bonded, |
| BtAclState::Disconnected, |
| BtAclState::Disconnected, |
| BluetoothDevice::new(*addr, "".to_string()), |
| Instant::now(), |
| vec![], |
| )); |
| } |
| |
| // Update the connectable mode since bonded device list might be updated. |
| self.update_connectable_mode(); |
| } |
| BluetoothProperty::BdName(bdname) => { |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_name_changed(bdname.clone()); |
| }); |
| } |
| _ => {} |
| } |
| |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_adapter_property_changed(prop.get_type()); |
| }); |
| } |
| } |
| |
| #[log_cb_args] |
| fn device_found(&mut self, _n: i32, properties: Vec<BluetoothProperty>) { |
| let device_info = BluetoothDevice::from_properties(&properties); |
| self.check_new_property_and_potentially_connect_profiles(device_info.address, &properties); |
| |
| let device_info = self |
| .remote_devices |
| .entry(device_info.address) |
| .and_modify(|d| { |
| d.update_properties(&properties); |
| d.seen(); |
| }) |
| .or_insert(BluetoothDeviceContext::new( |
| BtBondState::NotBonded, |
| BtAclState::Disconnected, |
| BtAclState::Disconnected, |
| device_info, |
| Instant::now(), |
| properties, |
| )) |
| .info |
| .clone(); |
| |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_device_found(device_info.clone()); |
| }); |
| } |
| |
| #[log_cb_args] |
| fn discovery_state(&mut self, state: BtDiscoveryState) { |
| 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 |
| self.is_discovering = &state == &BtDiscoveryState::Started; |
| if self.is_discovering { |
| self.discovering_started = Instant::now(); |
| } |
| |
| // Prevent sending out discovering changes or freshness checks when |
| // suspending. Clients don't need to be notified of discovery pausing |
| // during suspend. They will probably try to restore it and fail. |
| let discovery_suspend_mode = self.get_discovery_suspend_mode(); |
| if discovery_suspend_mode != SuspendMode::Normal |
| && discovery_suspend_mode != SuspendMode::Resuming |
| { |
| return; |
| } |
| |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_discovering_changed(state == BtDiscoveryState::Started); |
| }); |
| |
| // Start or stop BLE scanning based on discovering state |
| if let (Some(gatt), Some(scanner_id)) = (self.bluetooth_gatt.as_ref(), self.ble_scanner_id) |
| { |
| if is_discovering { |
| gatt.lock().unwrap().start_active_scan(scanner_id); |
| } else { |
| gatt.lock().unwrap().stop_active_scan(scanner_id); |
| } |
| } |
| |
| if !self.is_discovering && self.pending_create_bond.is_some() { |
| debug!("Invoking delayed CreateBond"); |
| let tx = self.tx.clone(); |
| tokio::spawn(async move { |
| let _ = tx.send(Message::AdapterActions(AdapterActions::CreateBond)).await; |
| }); |
| } |
| } |
| |
| #[log_cb_args] |
| fn ssp_request(&mut self, remote_addr: RawAddress, variant: BtSspVariant, passkey: u32) { |
| // Accept the Just-Works pairing that we initiated, reject otherwise. |
| if variant == BtSspVariant::Consent { |
| let initiated_by_us = Some(remote_addr) == self.active_pairing_address; |
| self.set_pairing_confirmation( |
| BluetoothDevice::new(remote_addr, "".to_string()), |
| initiated_by_us, |
| ); |
| return; |
| } |
| |
| // Currently this supports many agent because we accept many callbacks. |
| // TODO(b/274706838): We need a way to select the default agent. |
| self.callbacks.for_all_callbacks(|callback| { |
| // TODO(b/336960912): libbluetooth changed their API so that we no longer |
| // get the Device name and CoD, which were included in our DBus API. |
| // Now we simply put random values since we aren't ready to change our DBus API |
| // and it works because our Clients are not using these anyway. |
| callback.on_ssp_request( |
| BluetoothDevice::new(remote_addr, "".to_string()), |
| 0, |
| variant.clone(), |
| passkey, |
| ); |
| }); |
| } |
| |
| #[log_cb_args] |
| fn pin_request( |
| &mut self, |
| remote_addr: RawAddress, |
| remote_name: String, |
| cod: u32, |
| min_16_digit: bool, |
| ) { |
| let device = BluetoothDevice::new(remote_addr, remote_name.clone()); |
| |
| let digits = match min_16_digit { |
| true => 16, |
| false => 6, |
| }; |
| |
| if is_cod_hid_keyboard(cod) || is_cod_hid_combo(cod) { |
| debug!("auto gen pin for device {} (cod={:#x})", DisplayAddress(&remote_addr), cod); |
| // generate a random pin code to display. |
| let pin = rand::random::<u64>() % pow(10, digits); |
| let display_pin = format!("{:06}", pin); |
| |
| // Currently this supports many agent because we accept many callbacks. |
| // TODO(b/274706838): We need a way to select the default agent. |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_pin_display(device.clone(), display_pin.clone()); |
| }); |
| |
| let pin_vec = display_pin.chars().map(|d| d.try_into().unwrap()).collect::<Vec<u8>>(); |
| |
| self.set_pin(device, true, pin_vec); |
| } else { |
| debug!( |
| "sending pin request for device {} (cod={:#x}) to clients", |
| DisplayAddress(&remote_addr), |
| cod |
| ); |
| // Currently this supports many agent because we accept many callbacks. |
| // TODO(b/274706838): We need a way to select the default agent. |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_pin_request(device.clone(), cod, min_16_digit); |
| }); |
| } |
| } |
| |
| #[log_cb_args] |
| fn bond_state( |
| &mut self, |
| status: BtStatus, |
| addr: RawAddress, |
| bond_state: BtBondState, |
| fail_reason: i32, |
| ) { |
| // Get the device type before the device is potentially deleted. |
| let device_type = self.get_remote_type(BluetoothDevice::new(addr, "".to_string())); |
| |
| // Clear the pairing lock if this call corresponds to the |
| // active pairing device. |
| if bond_state != BtBondState::Bonding && self.active_pairing_address == Some(addr) { |
| self.active_pairing_address = None; |
| } |
| |
| if self.get_bond_state_by_addr(&addr) == bond_state { |
| debug!("[{}]: Unchanged bond_state", DisplayAddress(&addr)); |
| } else { |
| let entry = |
| self.remote_devices.entry(addr).and_modify(|d| d.bond_state = bond_state.clone()); |
| match bond_state { |
| BtBondState::NotBonded => { |
| if !self.get_wake_allowed_device_bonded() { |
| self.clear_uhid(); |
| } |
| // Update the connectable mode since bonded list is changed. |
| self.update_connectable_mode(); |
| } |
| BtBondState::Bonded => { |
| let device = entry.or_insert(BluetoothDeviceContext::new( |
| BtBondState::Bonded, |
| BtAclState::Disconnected, |
| BtAclState::Disconnected, |
| BluetoothDevice::new(addr, "".to_string()), |
| Instant::now(), |
| vec![], |
| )); |
| let device_info = device.info.clone(); |
| // Since this is a newly bonded device, we also need to trigger SDP on it. |
| self.fetch_remote_uuids(device_info); |
| if self.get_wake_allowed_device_bonded() { |
| self.create_uhid_for_suspend_wakesource(); |
| } |
| // Update the connectable mode since bonded list is changed. |
| self.update_connectable_mode(); |
| } |
| BtBondState::Bonding => {} |
| } |
| } |
| |
| // Modification to |self.remote_devices| has done, ok to fire the change event. |
| self.fire_device_connection_or_bonded_state_changed(addr); |
| |
| // Resume discovery once the bonding process is complete. Discovery was paused before the |
| // bond request to avoid ACL connection from interfering with active inquiry. |
| if bond_state == BtBondState::NotBonded || bond_state == BtBondState::Bonded { |
| self.resume_discovery(); |
| } |
| |
| // Send bond state changed notifications |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_bond_state_changed( |
| status.to_u32().unwrap(), |
| addr, |
| bond_state.to_u32().unwrap(), |
| ); |
| }); |
| |
| // Don't emit the metrics event if we were cancelling the bond. |
| // It is ok to not send the pairing complete event as the server should ignore the dangling |
| // pairing attempt event. |
| // This behavior aligns with BlueZ. |
| if !self.cancelling_devices.remove(&addr) { |
| metrics::bond_state_changed(addr, device_type, status, bond_state, fail_reason); |
| } |
| } |
| |
| #[log_cb_args] |
| fn remote_device_properties_changed( |
| &mut self, |
| _status: BtStatus, |
| addr: RawAddress, |
| _addr_type: u8, |
| _num_properties: i32, |
| properties: Vec<BluetoothProperty>, |
| ) { |
| self.check_new_property_and_potentially_connect_profiles(addr, &properties); |
| let device = self.remote_devices.entry(addr).or_insert(BluetoothDeviceContext::new( |
| BtBondState::NotBonded, |
| BtAclState::Disconnected, |
| BtAclState::Disconnected, |
| BluetoothDevice::new(addr, String::from("")), |
| Instant::now(), |
| vec![], |
| )); |
| |
| device.update_properties(&properties); |
| device.seen(); |
| |
| Bluetooth::send_metrics_remote_device_info(device); |
| |
| let info = device.info.clone(); |
| |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_device_properties_changed( |
| info.clone(), |
| properties.clone().into_iter().map(|x| x.get_type()).collect(), |
| ); |
| }); |
| |
| // Only care about device type property changed on bonded device. |
| // If the property change happens during bonding, it will be updated after bonding complete anyway. |
| if self.get_bond_state_by_addr(&addr) == BtBondState::Bonded |
| && properties.iter().any(|prop| match prop { |
| BluetoothProperty::TypeOfDevice(_) => true, |
| _ => false, |
| }) |
| { |
| // Update the connectable mode since the device type is changed. |
| self.update_connectable_mode(); |
| } |
| } |
| |
| #[log_cb_args] |
| fn acl_state( |
| &mut self, |
| status: BtStatus, |
| addr: RawAddress, |
| state: BtAclState, |
| link_type: BtTransport, |
| hci_reason: BtHciErrorCode, |
| conn_direction: BtConnectionDirection, |
| _acl_handle: u16, |
| ) { |
| // If discovery was previously paused at connect_all_enabled_profiles to avoid an outgoing |
| // ACL connection colliding with an ongoing inquiry, resume it. |
| self.resume_discovery(); |
| |
| if status != BtStatus::Success { |
| warn!( |
| "Connection to [{}] failed. Status: {:?}, Reason: {:?}", |
| DisplayAddress(&addr), |
| status, |
| hci_reason |
| ); |
| metrics::acl_connection_state_changed( |
| addr, |
| link_type, |
| status, |
| BtAclState::Disconnected, |
| conn_direction, |
| hci_reason, |
| ); |
| self.connection_callbacks.for_all_callbacks(|callback| { |
| callback.on_device_connection_failed( |
| BluetoothDevice::new(addr, String::from("")), |
| status, |
| ); |
| }); |
| return; |
| } |
| |
| let device = self.remote_devices.entry(addr).or_insert(BluetoothDeviceContext::new( |
| BtBondState::NotBonded, |
| BtAclState::Disconnected, |
| BtAclState::Disconnected, |
| BluetoothDevice::new(addr, String::from("")), |
| Instant::now(), |
| vec![], |
| )); |
| |
| // Only notify if there's been a change in state |
| if !device.set_transport_state(&link_type, &state) { |
| return; |
| } |
| |
| let info = device.info.clone(); |
| device.acl_reported_transport = link_type; |
| |
| metrics::acl_connection_state_changed( |
| addr, |
| link_type, |
| BtStatus::Success, |
| state.clone(), |
| conn_direction, |
| hci_reason, |
| ); |
| |
| match state { |
| BtAclState::Connected => { |
| Bluetooth::send_metrics_remote_device_info(device); |
| self.connection_callbacks.for_all_callbacks(|callback| { |
| callback.on_device_connected(info.clone()); |
| }); |
| } |
| BtAclState::Disconnected => { |
| if !device.is_connected() { |
| self.connection_callbacks.for_all_callbacks(|callback| { |
| callback.on_device_disconnected(info.clone()); |
| }); |
| device.connect_to_new_profiles = false; |
| } |
| } |
| }; |
| |
| // Modification to |self.remote_devices| has done, ok to fire the change event. |
| self.fire_device_connection_or_bonded_state_changed(addr); |
| |
| // If we are bonding, skip the update here as we will update it after bonding complete anyway. |
| // This is necessary for RTK controllers, which will break RNR after |Write Scan Enable| |
| // command. Although this is a bug of RTK controllers, but as we could avoid unwanted page |
| // scan, it makes sense to extend it to all BT controllers here. |
| if Some(addr) != self.active_pairing_address { |
| // Update the connectable since the connected state could be changed. |
| self.update_connectable_mode(); |
| } |
| } |
| |
| #[log_cb_args] |
| fn thread_event(&mut self, event: BtThreadEvent) { |
| match event { |
| BtThreadEvent::Associate => { |
| // Let the signal notifier know stack is initialized. |
| *self.sig_notifier.thread_attached.lock().unwrap() = true; |
| self.sig_notifier.thread_notify.notify_all(); |
| } |
| BtThreadEvent::Disassociate => { |
| // Let the signal notifier know stack is done. |
| *self.sig_notifier.thread_attached.lock().unwrap() = false; |
| self.sig_notifier.thread_notify.notify_all(); |
| } |
| } |
| } |
| |
| fn key_missing(&mut self, addr: RawAddress) { |
| if let Some(d) = self.remote_devices.get(&addr) { |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_device_key_missing(d.info.clone()); |
| }); |
| } |
| } |
| } |
| |
| struct BleDiscoveryCallbacks { |
| tx: Sender<Message>, |
| } |
| |
| impl BleDiscoveryCallbacks { |
| fn new(tx: Sender<Message>) -> Self { |
| Self { tx } |
| } |
| } |
| |
| // Handle BLE scanner results. |
| impl IScannerCallback for BleDiscoveryCallbacks { |
| #[log_cb_args] |
| fn on_scanner_registered(&mut self, uuid: Uuid, scanner_id: u8, status: GattStatus) { |
| let tx = self.tx.clone(); |
| tokio::spawn(async move { |
| let _ = tx |
| .send(Message::AdapterActions(AdapterActions::BleDiscoveryScannerRegistered( |
| uuid, scanner_id, status, |
| ))) |
| .await; |
| }); |
| } |
| |
| #[log_cb_args] |
| fn on_scan_result(&mut self, scan_result: ScanResult) { |
| let tx = self.tx.clone(); |
| tokio::spawn(async move { |
| let _ = tx |
| .send(Message::AdapterActions(AdapterActions::BleDiscoveryScannerResult( |
| scan_result, |
| ))) |
| .await; |
| }); |
| } |
| |
| fn on_advertisement_found(&mut self, _scanner_id: u8, _scan_result: ScanResult) {} |
| fn on_advertisement_lost(&mut self, _scanner_id: u8, _scan_result: ScanResult) {} |
| #[log_cb_args] |
| fn on_suspend_mode_change(&mut self, _suspend_mode: SuspendMode) {} |
| } |
| |
| impl RPCProxy for BleDiscoveryCallbacks { |
| fn get_object_id(&self) -> String { |
| "BLE Discovery Callback".to_string() |
| } |
| } |
| |
| // TODO: Add unit tests for this implementation |
| impl IBluetooth for Bluetooth { |
| fn register_callback(&mut self, callback: Box<dyn IBluetoothCallback + Send>) -> u32 { |
| self.callbacks.add_callback(callback) |
| } |
| |
| fn unregister_callback(&mut self, callback_id: u32) -> bool { |
| self.callbacks.remove_callback(callback_id) |
| } |
| |
| fn register_connection_callback( |
| &mut self, |
| callback: Box<dyn IBluetoothConnectionCallback + Send>, |
| ) -> u32 { |
| self.connection_callbacks.add_callback(callback) |
| } |
| |
| fn unregister_connection_callback(&mut self, callback_id: u32) -> bool { |
| self.connection_callbacks.remove_callback(callback_id) |
| } |
| |
| fn init(&mut self, hci_index: i32) -> bool { |
| self.intf.lock().unwrap().initialize(get_bt_dispatcher(self.tx.clone()), hci_index) |
| } |
| |
| fn enable(&mut self) -> bool { |
| self.disabling = false; |
| self.intf.lock().unwrap().enable() == 0 |
| } |
| |
| fn disable(&mut self) -> bool { |
| self.shutdown_adapter(false) |
| } |
| |
| fn cleanup(&mut self) { |
| self.intf.lock().unwrap().cleanup(); |
| } |
| |
| fn get_address(&self) -> RawAddress { |
| self.local_address.unwrap_or_default() |
| } |
| |
| fn get_uuids(&self) -> Vec<Uuid> { |
| match self.properties.get(&BtPropertyType::Uuids) { |
| Some(prop) => match prop { |
| BluetoothProperty::Uuids(uuids) => uuids.clone(), |
| _ => vec![], |
| }, |
| _ => vec![], |
| } |
| } |
| |
| fn get_name(&self) -> String { |
| match self.properties.get(&BtPropertyType::BdName) { |
| Some(prop) => match prop { |
| BluetoothProperty::BdName(name) => name.clone(), |
| _ => String::new(), |
| }, |
| _ => String::new(), |
| } |
| } |
| |
| fn set_name(&self, name: String) -> bool { |
| if self.get_name() == name { |
| return true; |
| } |
| self.intf.lock().unwrap().set_adapter_property(BluetoothProperty::BdName(name)) == 0 |
| } |
| |
| fn get_bluetooth_class(&self) -> u32 { |
| match self.properties.get(&BtPropertyType::ClassOfDevice) { |
| Some(prop) => match prop { |
| BluetoothProperty::ClassOfDevice(cod) => *cod, |
| _ => 0, |
| }, |
| _ => 0, |
| } |
| } |
| |
| fn set_bluetooth_class(&self, cod: u32) -> bool { |
| self.intf.lock().unwrap().set_adapter_property(BluetoothProperty::ClassOfDevice(cod)) == 0 |
| } |
| |
| fn get_discoverable(&self) -> bool { |
| self.get_discoverable_mode_internal() != BtDiscMode::NonDiscoverable |
| } |
| |
| fn get_discoverable_timeout(&self) -> u32 { |
| self.discoverable_duration |
| } |
| |
| fn set_discoverable(&mut self, mode: BtDiscMode, duration: u32) -> bool { |
| let intf = self.intf.lock().unwrap(); |
| |
| // Checks if the duration is valid. |
| if mode == BtDiscMode::LimitedDiscoverable && (duration > 60 || duration == 0) { |
| warn!("Invalid duration for setting the device into limited discoverable mode. The valid duration is 1~60 seconds."); |
| return false; |
| } |
| |
| // Don't really set the mode when suspend. The mode would be instead restored on resume. |
| // However, we still need to set the discoverable timeout so it would properly reset |
| // |self.discoverable_mode| after resume. |
| if self.get_scan_suspend_mode() == SuspendMode::Normal { |
| let scan_mode = match mode { |
| BtDiscMode::LimitedDiscoverable => BtScanMode::ConnectableLimitedDiscoverable, |
| BtDiscMode::GeneralDiscoverable => BtScanMode::ConnectableDiscoverable, |
| BtDiscMode::NonDiscoverable => match self.is_connectable { |
| true => BtScanMode::Connectable, |
| false => BtScanMode::None_, |
| }, |
| }; |
| intf.set_scan_mode(scan_mode); |
| } |
| |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_discoverable_changed(mode == BtDiscMode::GeneralDiscoverable); |
| }); |
| self.discoverable_mode = mode.clone(); |
| self.discoverable_duration = duration; |
| |
| // The old timer should be overwritten regardless of what the new mode is. |
| if let Some(handle) = self.discoverable_timeout.take() { |
| handle.abort(); |
| } |
| |
| if mode != BtDiscMode::NonDiscoverable && duration != 0 { |
| let txl = self.tx.clone(); |
| self.discoverable_timeout = Some(tokio::spawn(async move { |
| time::sleep(Duration::from_secs(duration.into())).await; |
| let _ = txl.send(Message::AdapterActions(AdapterActions::ResetDiscoverable)).await; |
| })); |
| } |
| |
| true |
| } |
| |
| fn is_multi_advertisement_supported(&self) -> bool { |
| match self.properties.get(&BtPropertyType::LocalLeFeatures) { |
| Some(prop) => match prop { |
| BluetoothProperty::LocalLeFeatures(llf) => { |
| llf.max_adv_instance >= MIN_ADV_INSTANCES_FOR_MULTI_ADV |
| } |
| _ => false, |
| }, |
| _ => false, |
| } |
| } |
| |
| fn is_le_extended_advertising_supported(&self) -> bool { |
| match self.properties.get(&BtPropertyType::LocalLeFeatures) { |
| Some(prop) => match prop { |
| BluetoothProperty::LocalLeFeatures(llf) => llf.le_extended_advertising_supported, |
| _ => false, |
| }, |
| _ => false, |
| } |
| } |
| |
| fn start_discovery(&mut self) -> bool { |
| // Short-circuit to avoid sending multiple start discovery calls. |
| if self.is_discovering { |
| return true; |
| } |
| |
| // Short-circuit if paused and add the discovery intent to the queue. |
| if self.is_discovery_paused { |
| self.pending_discovery = true; |
| debug!("Queue the discovery request during paused state"); |
| return true; |
| } |
| |
| let discovery_suspend_mode = self.get_discovery_suspend_mode(); |
| if discovery_suspend_mode != SuspendMode::Normal |
| && discovery_suspend_mode != SuspendMode::Resuming |
| { |
| log::warn!("start_discovery is not allowed when suspending or suspended."); |
| return false; |
| } |
| |
| self.intf.lock().unwrap().start_discovery() == 0 |
| } |
| |
| fn cancel_discovery(&mut self) -> bool { |
| // Client no longer want to discover, clear the request |
| if self.is_discovery_paused { |
| self.pending_discovery = false; |
| debug!("Cancel the discovery request during paused state"); |
| } |
| |
| // Reject the cancel discovery request if the underlying stack is not in a discovering |
| // state. For example, previous start discovery was enqueued for ongoing discovery. |
| if !self.is_discovering { |
| debug!("Reject cancel_discovery as it's not in discovering state."); |
| return false; |
| } |
| |
| let discovery_suspend_mode = self.get_discovery_suspend_mode(); |
| if discovery_suspend_mode != SuspendMode::Normal |
| && discovery_suspend_mode != SuspendMode::Suspending |
| { |
| log::warn!("cancel_discovery is not allowed when resuming or suspended."); |
| return false; |
| } |
| |
| self.intf.lock().unwrap().cancel_discovery() == 0 |
| } |
| |
| fn is_discovering(&self) -> bool { |
| self.is_discovering |
| } |
| |
| fn get_discovery_end_millis(&self) -> u64 { |
| if !self.is_discovering { |
| return 0; |
| } |
| |
| let elapsed_ms = self.discovering_started.elapsed().as_millis() as u64; |
| if elapsed_ms >= DEFAULT_DISCOVERY_TIMEOUT_MS { |
| 0 |
| } else { |
| DEFAULT_DISCOVERY_TIMEOUT_MS - elapsed_ms |
| } |
| } |
| |
| fn create_bond(&mut self, device: BluetoothDevice, transport: BtTransport) -> BtStatus { |
| let device_type = match transport { |
| BtTransport::Bredr => BtDeviceType::Bredr, |
| BtTransport::Le => BtDeviceType::Ble, |
| _ => self.get_remote_type(device.clone()), |
| }; |
| let address = device.address; |
| |
| if let Some(active_address) = self.active_pairing_address { |
| warn!( |
| "Bonding requested for {} while already bonding {}, rejecting", |
| DisplayAddress(&address), |
| DisplayAddress(&active_address) |
| ); |
| return BtStatus::Busy; |
| } |
| |
| if self.pending_create_bond.is_some() { |
| warn!("Delayed CreateBond is still pending"); |
| return BtStatus::Busy; |
| } |
| |
| // There could be a race between bond complete and bond cancel, which makes |
| // |cancelling_devices| in a wrong state. Remove the device just in case. |
| if self.cancelling_devices.remove(&address) { |
| warn!("Device {} is also cancelling the bond.", DisplayAddress(&address)); |
| } |
| |
| // BREDR connection won't work when Inquiry / Remote Name Request is in progress. |
| // If is_discovering, delay the request until discovery state change. |
| if self.is_discovering { |
| debug!("Discovering. Delay the CreateBond request until discovery is done."); |
| self.pause_discovery(); |
| self.pending_create_bond = Some((device, transport)); |
| return BtStatus::Success; |
| } |
| |
| // We explicitly log the attempt to start the bonding separate from logging the bond state. |
| // The start of the attempt is critical to help identify a bonding/pairing session. |
| metrics::bond_create_attempt(address, device_type.clone()); |
| |
| self.active_pairing_address = Some(address); |
| let status = self.intf.lock().unwrap().create_bond(&address, transport); |
| |
| if status != 0 { |
| metrics::bond_state_changed( |
| address, |
| device_type, |
| BtStatus::from(status as u32), |
| BtBondState::NotBonded, |
| 0, |
| ); |
| return BtStatus::from(status as u32); |
| } |
| |
| // Creating bond automatically create ACL connection as well, therefore also log metrics |
| // ACL connection attempt here. |
| if !self.get_acl_state_by_addr(&address) { |
| metrics::acl_connect_attempt(address, BtAclState::Connected); |
| } |
| |
| BtStatus::Success |
| } |
| |
| fn cancel_bond_process(&mut self, device: BluetoothDevice) -> bool { |
| if !self.cancelling_devices.insert(device.address) { |
| warn!( |
| "Device {} has been added to cancelling_device.", |
| DisplayAddress(&device.address) |
| ); |
| } |
| |
| self.intf.lock().unwrap().cancel_bond(&device.address) == 0 |
| } |
| |
| fn remove_bond(&mut self, device: BluetoothDevice) -> bool { |
| let address = device.address; |
| |
| // There could be a race between bond complete and bond cancel, which makes |
| // |cancelling_devices| in a wrong state. Remove the device just in case. |
| if self.cancelling_devices.remove(&address) { |
| warn!("Device {} is also cancelling the bond.", DisplayAddress(&address)); |
| } |
| |
| let status = self.intf.lock().unwrap().remove_bond(&address); |
| |
| if status != 0 { |
| return false; |
| } |
| |
| // Removing bond also disconnects the ACL if is connected. Therefore, also log ACL |
| // disconnection attempt here. |
| if self.get_acl_state_by_addr(&address) { |
| metrics::acl_connect_attempt(address, BtAclState::Disconnected); |
| } |
| |
| true |
| } |
| |
| fn get_bonded_devices(&self) -> Vec<BluetoothDevice> { |
| self.remote_devices |
| .values() |
| .filter_map(|d| { |
| if d.bond_state == BtBondState::Bonded { |
| Some(d.info.clone()) |
| } else { |
| None |
| } |
| }) |
| .collect() |
| } |
| |
| fn get_bond_state(&self, device: BluetoothDevice) -> BtBondState { |
| self.get_bond_state_by_addr(&device.address) |
| } |
| |
| fn set_pin(&self, device: BluetoothDevice, accept: bool, pin_code: Vec<u8>) -> bool { |
| if self.get_bond_state_by_addr(&device.address) != BtBondState::Bonding { |
| warn!("Can't set pin. Device {} isn't bonding.", DisplayAddress(&device.address)); |
| return false; |
| } |
| |
| let mut btpin = BtPinCode { pin: array_utils::to_sized_array(&pin_code) }; |
| |
| self.intf.lock().unwrap().pin_reply( |
| &device.address, |
| accept as u8, |
| pin_code.len() as u8, |
| &mut btpin, |
| ) == 0 |
| } |
| |
| fn set_passkey(&self, device: BluetoothDevice, accept: bool, passkey: Vec<u8>) -> bool { |
| if self.get_bond_state_by_addr(&device.address) != BtBondState::Bonding { |
| warn!("Can't set passkey. Device {} isn't bonding.", DisplayAddress(&device.address)); |
| return false; |
| } |
| |
| let mut tmp: [u8; 4] = [0; 4]; |
| tmp.copy_from_slice(passkey.as_slice()); |
| let passkey = u32::from_ne_bytes(tmp); |
| |
| self.intf.lock().unwrap().ssp_reply( |
| &device.address, |
| BtSspVariant::PasskeyEntry, |
| accept as u8, |
| passkey, |
| ) == 0 |
| } |
| |
| fn set_pairing_confirmation(&self, device: BluetoothDevice, accept: bool) -> bool { |
| self.intf.lock().unwrap().ssp_reply( |
| &device.address, |
| BtSspVariant::PasskeyConfirmation, |
| accept as u8, |
| 0, |
| ) == 0 |
| } |
| |
| fn get_remote_name(&self, device: BluetoothDevice) -> String { |
| match self.get_remote_device_property(&device, &BtPropertyType::BdName) { |
| Some(BluetoothProperty::BdName(name)) => name.clone(), |
| _ => "".to_string(), |
| } |
| } |
| |
| fn get_remote_type(&self, device: BluetoothDevice) -> BtDeviceType { |
| match self.get_remote_device_property(&device, &BtPropertyType::TypeOfDevice) { |
| Some(BluetoothProperty::TypeOfDevice(device_type)) => device_type, |
| _ => BtDeviceType::Unknown, |
| } |
| } |
| |
| fn get_remote_alias(&self, device: BluetoothDevice) -> String { |
| match self.get_remote_device_property(&device, &BtPropertyType::RemoteFriendlyName) { |
| Some(BluetoothProperty::RemoteFriendlyName(name)) => name.clone(), |
| _ => "".to_string(), |
| } |
| } |
| |
| fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String) { |
| let _ = self.set_remote_device_property( |
| &device, |
| BtPropertyType::RemoteFriendlyName, |
| BluetoothProperty::RemoteFriendlyName(new_alias), |
| ); |
| } |
| |
| fn get_remote_class(&self, device: BluetoothDevice) -> u32 { |
| match self.get_remote_device_property(&device, &BtPropertyType::ClassOfDevice) { |
| Some(BluetoothProperty::ClassOfDevice(class)) => class, |
| _ => 0, |
| } |
| } |
| |
| fn get_remote_appearance(&self, device: BluetoothDevice) -> u16 { |
| match self.get_remote_device_property(&device, &BtPropertyType::Appearance) { |
| Some(BluetoothProperty::Appearance(appearance)) => appearance, |
| _ => 0, |
| } |
| } |
| |
| fn get_remote_connected(&self, device: BluetoothDevice) -> bool { |
| self.get_connection_state(device) != BtConnectionState::NotConnected |
| } |
| |
| fn get_remote_wake_allowed(&self, device: BluetoothDevice) -> bool { |
| // Wake is allowed if the device supports HIDP or HOGP only. |
| match self.get_remote_device_property(&device, &BtPropertyType::Uuids) { |
| Some(BluetoothProperty::Uuids(uuids)) => { |
| return uuids.iter().any(|&uuid| { |
| UuidHelper::is_known_profile(&uuid).map_or(false, |profile| { |
| profile == Profile::Hid || profile == Profile::Hogp |
| }) |
| }); |
| } |
| _ => false, |
| } |
| } |
| |
| fn get_remote_vendor_product_info(&self, device: BluetoothDevice) -> BtVendorProductInfo { |
| match self.get_remote_device_property(&device, &BtPropertyType::VendorProductInfo) { |
| Some(BluetoothProperty::VendorProductInfo(p)) => p, |
| _ => BtVendorProductInfo { vendor_id_src: 0, vendor_id: 0, product_id: 0, version: 0 }, |
| } |
| } |
| |
| fn get_remote_address_type(&self, device: BluetoothDevice) -> BtAddrType { |
| match self.get_remote_device_property(&device, &BtPropertyType::RemoteAddrType) { |
| Some(BluetoothProperty::RemoteAddrType(addr_type)) => addr_type, |
| _ => BtAddrType::Unknown, |
| } |
| } |
| |
| fn get_remote_rssi(&self, device: BluetoothDevice) -> i8 { |
| match self.get_remote_device_property(&device, &BtPropertyType::RemoteRssi) { |
| Some(BluetoothProperty::RemoteRssi(rssi)) => rssi, |
| _ => INVALID_RSSI, |
| } |
| } |
| |
| fn get_connected_devices(&self) -> Vec<BluetoothDevice> { |
| self.remote_devices |
| .values() |
| .filter_map(|d| if d.is_connected() { Some(d.info.clone()) } else { None }) |
| .collect() |
| } |
| |
| fn get_connection_state(&self, device: BluetoothDevice) -> BtConnectionState { |
| // The underlying api adds whether this is ENCRYPTED_BREDR or ENCRYPTED_LE. |
| // As long as it is non-zero, it is connected. |
| self.intf.lock().unwrap().get_connection_state(&device.address) |
| } |
| |
| fn get_profile_connection_state(&self, profile: Uuid) -> ProfileConnectionState { |
| if let Some(known) = UuidHelper::is_known_profile(&profile) { |
| match known { |
| Profile::A2dpSink | Profile::A2dpSource => self |
| .bluetooth_media |
| .as_ref() |
| .map_or(ProfileConnectionState::Disconnected, |media| { |
| media.lock().unwrap().get_a2dp_connection_state() |
| }), |
| Profile::Hfp | Profile::HfpAg => self |
| .bluetooth_media |
| .as_ref() |
| .map_or(ProfileConnectionState::Disconnected, |media| { |
| media.lock().unwrap().get_hfp_connection_state() |
| }), |
| // TODO: (b/223431229) Profile::Hid and Profile::Hogp |
| _ => ProfileConnectionState::Disconnected, |
| } |
| } else { |
| ProfileConnectionState::Disconnected |
| } |
| } |
| |
| fn get_remote_uuids(&self, device: BluetoothDevice) -> Vec<Uuid> { |
| match self.get_remote_device_property(&device, &BtPropertyType::Uuids) { |
| Some(BluetoothProperty::Uuids(uuids)) => uuids, |
| _ => vec![], |
| } |
| } |
| |
| fn fetch_remote_uuids(&self, remote_device: BluetoothDevice) -> bool { |
| let Some(device) = self.remote_devices.get(&remote_device.address) else { |
| warn!("Won't fetch UUIDs on unknown device"); |
| return false; |
| }; |
| |
| let transport = match self.get_remote_type(device.info.clone()) { |
| BtDeviceType::Bredr => BtTransport::Bredr, |
| BtDeviceType::Ble => BtTransport::Le, |
| _ => device.acl_reported_transport, |
| }; |
| |
| self.intf.lock().unwrap().get_remote_services(&mut device.info.address.clone(), transport) |
| == 0 |
| } |
| |
| fn sdp_search(&self, mut device: BluetoothDevice, uuid: Uuid) -> bool { |
| if let Some(sdp) = self.sdp.as_ref() { |
| return sdp.sdp_search(&mut device.address, &uuid) == BtStatus::Success; |
| } |
| false |
| } |
| |
| fn create_sdp_record(&mut self, sdp_record: BtSdpRecord) -> bool { |
| let mut handle: i32 = -1; |
| let mut sdp_record = sdp_record; |
| match self.sdp.as_ref().unwrap().create_sdp_record(&mut sdp_record, &mut handle) { |
| BtStatus::Success => { |
| let record_clone = sdp_record.clone(); |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_sdp_record_created(record_clone.clone(), handle); |
| }); |
| true |
| } |
| _ => false, |
| } |
| } |
| |
| fn remove_sdp_record(&self, handle: i32) -> bool { |
| self.sdp.as_ref().unwrap().remove_sdp_record(handle) == BtStatus::Success |
| } |
| |
| fn connect_all_enabled_profiles(&mut self, device: BluetoothDevice) -> BtStatus { |
| // Profile init must be complete before this api is callable |
| if !self.profiles_ready { |
| return BtStatus::NotReady; |
| } |
| |
| // Check all remote uuids to see if they match enabled profiles and connect them. |
| let uuids = self.get_remote_uuids(device.clone()); |
| self.connect_profiles_internal(&uuids, device.clone()); |
| |
| // Also connect to profiles discovered in the future. |
| if let Some(d) = self.remote_devices.get_mut(&device.address) { |
| d.connect_to_new_profiles = true; |
| } |
| |
| BtStatus::Success |
| } |
| |
| fn disconnect_all_enabled_profiles(&mut self, device: BluetoothDevice) -> bool { |
| if !self.profiles_ready { |
| return false; |
| } |
| let addr = device.address; |
| |
| // log ACL disconnection attempt if it's not already disconnected. |
| if self.get_acl_state_by_addr(&addr) { |
| metrics::acl_connect_attempt(addr, BtAclState::Disconnected); |
| } |
| |
| let uuids = self.get_remote_uuids(device.clone()); |
| let mut has_classic_media_profile = false; |
| let mut has_le_media_profile = false; |
| for uuid in uuids.iter() { |
| match UuidHelper::is_known_profile(uuid) { |
| Some(p) => { |
| if UuidHelper::is_profile_supported(&p) { |
| match p { |
| Profile::Hid | Profile::Hogp => { |
| // TODO(b/328675014): Use BtAddrType |
| // and BtTransport from |
| // BluetoothDevice instead of default |
| |
| // TODO(b/329837967): Determine |
| // correct reconnection behavior based |
| // on device instead of the default |
| self.hh.as_ref().unwrap().disconnect( |
| &mut addr.clone(), |
| BtAddrType::Public, |
| BtTransport::Auto, |
| /*reconnect_allowed=*/ true, |
| ); |
| } |
| |
| // TODO(b/317682584): implement policy to disconnect from LEA, VC, and CSIS |
| Profile::LeAudio | Profile::VolumeControl | Profile::CoordinatedSet |
| if !has_le_media_profile => |
| { |
| has_le_media_profile = true; |
| let txl = self.tx.clone(); |
| topstack::get_runtime().spawn(async move { |
| let _ = txl |
| .send(Message::Media( |
| MediaActions::DisconnectLeaGroupByMemberAddress(addr), |
| )) |
| .await; |
| }); |
| } |
| |
| Profile::A2dpSink |
| | Profile::A2dpSource |
| | Profile::Hfp |
| | Profile::AvrcpController |
| if !has_classic_media_profile => |
| { |
| has_classic_media_profile = true; |
| let txl = self.tx.clone(); |
| topstack::get_runtime().spawn(async move { |
| let _ = txl |
| .send(Message::Media(MediaActions::Disconnect(addr))) |
| .await; |
| }); |
| } |
| |
| // We don't connect most profiles |
| _ => (), |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| |
| // Disconnect all socket connections |
| let txl = self.tx.clone(); |
| topstack::get_runtime().spawn(async move { |
| let _ = |
| txl.send(Message::SocketManagerActions(SocketActions::DisconnectAll(addr))).await; |
| }); |
| |
| // Disconnect all GATT connections |
| let txl = self.tx.clone(); |
| topstack::get_runtime().spawn(async move { |
| let _ = txl.send(Message::GattActions(GattActions::Disconnect(device))).await; |
| }); |
| |
| if let Some(d) = self.remote_devices.get_mut(&addr) { |
| d.connect_to_new_profiles = false; |
| } |
| |
| true |
| } |
| |
| fn is_wbs_supported(&self) -> bool { |
| self.intf.lock().unwrap().get_wbs_supported() |
| } |
| |
| fn is_swb_supported(&self) -> bool { |
| self.intf.lock().unwrap().get_swb_supported() |
| } |
| |
| fn get_supported_roles(&self) -> Vec<BtAdapterRole> { |
| let mut roles: Vec<BtAdapterRole> = vec![]; |
| |
| // See Core 5.3, Vol 4, Part E, 7.8.27 for detailed state information |
| if self.le_supported_states >> 35 & 1 == 1u64 { |
| roles.push(BtAdapterRole::Central); |
| } |
| if self.le_supported_states >> 38 & 1 == 1u64 { |
| roles.push(BtAdapterRole::Peripheral); |
| } |
| if self.le_supported_states >> 28 & 1 == 1u64 { |
| roles.push(BtAdapterRole::CentralPeripheral); |
| } |
| |
| roles |
| } |
| |
| fn is_coding_format_supported(&self, coding_format: EscoCodingFormat) -> bool { |
| self.intf.lock().unwrap().is_coding_format_supported(coding_format as u8) |
| } |
| |
| fn is_le_audio_supported(&self) -> bool { |
| // We determine LE Audio support by checking CIS Central support |
| // See Core 5.3, Vol 6, 4.6 FEATURE SUPPORT |
| self.le_local_supported_features >> 28 & 1 == 1u64 |
| } |
| |
| fn is_dual_mode_audio_sink_device(&self, device: BluetoothDevice) -> bool { |
| fn is_dual_mode(uuids: Vec<Uuid>) -> bool { |
| fn get_unwrapped_uuid(profile: Profile) -> Uuid { |
| *UuidHelper::get_profile_uuid(&profile).unwrap_or(&Uuid::empty()) |
| } |
| |
| uuids.contains(&get_unwrapped_uuid(Profile::LeAudio)) |
| && (uuids.contains(&get_unwrapped_uuid(Profile::A2dpSink)) |
| || uuids.contains(&get_unwrapped_uuid(Profile::Hfp))) |
| } |
| |
| let Some(media) = self.bluetooth_media.as_ref() else { |
| return false; |
| }; |
| let media = media.lock().unwrap(); |
| let group_id = media.get_group_id(device.address); |
| if group_id == LEA_UNKNOWN_GROUP_ID { |
| return is_dual_mode(self.get_remote_uuids(device)); |
| } |
| |
| // Check if any device in the CSIP group is a dual mode audio sink device |
| media.get_group_devices(group_id).iter().any(|addr| { |
| is_dual_mode(self.get_remote_uuids(BluetoothDevice::new(*addr, "".to_string()))) |
| }) |
| } |
| |
| fn get_dumpsys(&self) -> String { |
| OpenOptions::new() |
| .write(true) |
| .create(true) |
| .truncate(true) |
| .open(DUMPSYS_LOG) |
| .and_then(|file| { |
| let fd = file.as_raw_fd(); |
| self.intf.lock().unwrap().dump(fd); |
| Ok(format!("dump to {}", DUMPSYS_LOG)) |
| }) |
| .unwrap_or_default() |
| } |
| } |
| |
| impl BtifSdpCallbacks for Bluetooth { |
| #[log_cb_args] |
| fn sdp_search( |
| &mut self, |
| status: BtStatus, |
| address: RawAddress, |
| uuid: Uuid, |
| _count: i32, |
| records: Vec<BtSdpRecord>, |
| ) { |
| let device_info = match self.remote_devices.get(&address) { |
| Some(d) => d.info.clone(), |
| None => BluetoothDevice::new(address, "".to_string()), |
| }; |
| |
| // The SDP records we get back do not populate the UUID so we populate it ourselves before |
| // sending them on. |
| let mut records = records; |
| records.iter_mut().for_each(|record| { |
| match record { |
| BtSdpRecord::HeaderOverlay(header) => header.uuid = uuid, |
| BtSdpRecord::MapMas(record) => record.hdr.uuid = uuid, |
| BtSdpRecord::MapMns(record) => record.hdr.uuid = uuid, |
| BtSdpRecord::PbapPse(record) => record.hdr.uuid = uuid, |
| BtSdpRecord::PbapPce(record) => record.hdr.uuid = uuid, |
| BtSdpRecord::OppServer(record) => record.hdr.uuid = uuid, |
| BtSdpRecord::SapServer(record) => record.hdr.uuid = uuid, |
| BtSdpRecord::Dip(record) => record.hdr.uuid = uuid, |
| BtSdpRecord::Mps(record) => record.hdr.uuid = uuid, |
| }; |
| }); |
| self.callbacks.for_all_callbacks(|callback| { |
| callback.on_sdp_search_complete(device_info.clone(), uuid, records.clone()); |
| }); |
| debug!( |
| "Sdp search result found: Status={:?} Address={} Uuid={}", |
| status, |
| DisplayAddress(&address), |
| DisplayUuid(&uuid) |
| ); |
| } |
| } |
| |
| impl BtifHHCallbacks for Bluetooth { |
| #[log_cb_args] |
| fn connection_state( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| state: BthhConnectionState, |
| ) { |
| // HID or HOG is not differentiated by the hid host when callback this function. Assume HOG |
| // if the device is LE only and HID if classic only. And assume HOG if UUID said so when |
| // device type is dual or unknown. |
| let device = BluetoothDevice::new(address, "".to_string()); |
| let profile = match self.get_remote_type(device.clone()) { |
| BtDeviceType::Ble => Profile::Hogp, |
| BtDeviceType::Bredr => Profile::Hid, |
| _ => { |
| if self |
| .get_remote_uuids(device) |
| .contains(UuidHelper::get_profile_uuid(&Profile::Hogp).unwrap()) |
| { |
| Profile::Hogp |
| } else { |
| Profile::Hid |
| } |
| } |
| }; |
| |
| metrics::profile_connection_state_changed( |
| address, |
| profile as u32, |
| BtStatus::Success, |
| state as u32, |
| ); |
| |
| let tx = self.tx.clone(); |
| self.remote_devices.entry(address).and_modify(|context| { |
| if context.is_initiated_hh_connection |
| && (state != BthhConnectionState::Connected |
| && state != BthhConnectionState::Connecting) |
| { |
| tokio::spawn(async move { |
| let _ = tx.send(Message::ProfileDisconnected(address)).await; |
| }); |
| } |
| context.is_initiated_hh_connection = |
| state == BthhConnectionState::Connected || state == BthhConnectionState::Connecting; |
| }); |
| |
| if BtBondState::Bonded != self.get_bond_state_by_addr(&address) |
| && (state != BthhConnectionState::Disconnecting |
| && state != BthhConnectionState::Disconnected) |
| { |
| warn!( |
| "[{}]: Rejecting a unbonded device's attempt to connect to HID/HOG profiles", |
| DisplayAddress(&address) |
| ); |
| // TODO(b/329837967): Determine correct reconnection |
| // behavior based on device instead of the default |
| let mut address = address; |
| self.hh.as_ref().unwrap().disconnect( |
| &mut address, |
| address_type, |
| transport, |
| /*reconnect_allowed=*/ true, |
| ); |
| } |
| } |
| |
| #[log_cb_args] |
| fn hid_info( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| info: BthhHidInfo, |
| ) { |
| } |
| |
| #[log_cb_args] |
| fn protocol_mode( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| mode: BthhProtocolMode, |
| ) { |
| } |
| |
| #[log_cb_args] |
| fn idle_time( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| idle_rate: i32, |
| ) { |
| } |
| |
| #[log_cb_args] |
| fn get_report( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| _data: Vec<u8>, |
| size: i32, |
| ) { |
| } |
| |
| #[log_cb_args] |
| fn handshake( |
| &mut self, |
| address: RawAddress, |
| address_type: BtAddrType, |
| transport: BtTransport, |
| status: BthhStatus, |
| ) { |
| } |
| } |
| |
| // TODO(b/261143122): Remove these once we migrate to BluetoothQA entirely |
| impl IBluetoothQALegacy for Bluetooth { |
| fn get_connectable(&self) -> bool { |
| self.get_connectable_internal() |
| } |
| |
| fn set_connectable(&mut self, mode: bool) -> bool { |
| self.set_connectable_internal(mode) |
| } |
| |
| fn get_alias(&self) -> String { |
| self.get_alias_internal() |
| } |
| |
| fn get_modalias(&self) -> String { |
| format!("bluetooth:v00E0pC405d{:04x}", FLOSS_VER) |
| } |
| |
| fn get_hid_report( |
| &mut self, |
| addr: RawAddress, |
| report_type: BthhReportType, |
| report_id: u8, |
| ) -> BtStatus { |
| self.get_hid_report_internal(addr, report_type, report_id) |
| } |
| |
| fn set_hid_report( |
| &mut self, |
| addr: RawAddress, |
| report_type: BthhReportType, |
| report: String, |
| ) -> BtStatus { |
| self.set_hid_report_internal(addr, report_type, report) |
| } |
| |
| fn send_hid_data(&mut self, addr: RawAddress, data: String) -> BtStatus { |
| self.send_hid_data_internal(addr, data) |
| } |
| } |