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

Commit 15831bac authored by Frédéric Danis's avatar Frédéric Danis
Browse files

Floss: Add UHID device creation

The UHID device is created when SLC is completed. It is destroyed when the
link with the headset disappear.

Send hook switch status to UHID on HF ATA or AT+CHUP

Manage UHID events by starting a blocking thread to read UHID output
events. This needs to duplicate the UHID file descriptor, and so the
uhid_virt crate is only used to generate or parse UHID events.

Bug: 277693919
Tag: #floss
Test: Conduct the following manual tests.
  $ btclient -c "telephony enable"
  pair Bluetooth Headset
  load https://google.git.io/libhidtelephoony/ with Chrome
  click on "Start", select the headset then "Connect"
  "Inputs Reports" and "Output Reports" are correct
  "Hook Switch", "Off-Hook" and "Ring" test cases are passed
Test: atest bluetooth_test_gd

Change-Id: Icae09ba0e3434816bbe3ad22f759b5c9685fc54c
parent 2dcade2c
Loading
Loading
Loading
Loading
+124 −0
Original line number Diff line number Diff line
@@ -19,6 +19,10 @@ use bt_topshim::profiles::hfp::{
use bt_topshim::profiles::ProfileConnectionState;
use bt_topshim::{metrics, topstack};
use bt_utils::at_command_parser::{calculate_battery_percent, parse_at_command_data};
use bt_utils::uhid_hfp::{
    OutputEvent, UHidHfp, BLUETOOTH_TELEPHONY_UHID_REPORT_ID, UHID_OUTPUT_NONE,
    UHID_OUTPUT_OFF_HOOK, UHID_OUTPUT_RING,
};
use bt_utils::uinput::UInput;

use itertools::Itertools;
@@ -290,6 +294,7 @@ pub struct BluetoothMedia {
    phone_ops_enabled: bool,
    memory_dialing_number: Option<String>,
    last_dialing_number: Option<String>,
    uhid: HashMap<RawAddress, UHidHfp>,
}

impl BluetoothMedia {
@@ -337,6 +342,7 @@ impl BluetoothMedia {
            phone_ops_enabled: false,
            memory_dialing_number: None,
            last_dialing_number: None,
            uhid: HashMap::new(),
        }
    }

@@ -685,9 +691,12 @@ impl BluetoothMedia {
                                HfpCodecCapability::NONE,
                            );
                        }

                        self.uhid_create(addr);
                    }
                    BthfConnectionState::Disconnected => {
                        info!("[{}]: hfp disconnected.", DisplayAddress(&addr));
                        self.uhid_destroy(&addr);
                        self.hfp_states.remove(&addr);
                        self.hfp_cap.remove(&addr);
                        self.hfp_audio_state.remove(&addr);
@@ -916,6 +925,7 @@ impl BluetoothMedia {

                debug!("[{}]: Start SCO call due to ATA", DisplayAddress(&addr));
                self.start_sco_call_impl(addr.to_string(), false, HfpCodecCapability::NONE);
                self.uhid_send_hook_switch_status(&addr, true);
            }
            HfpCallbacks::HangupCall(addr) => {
                if !self.hangup_call_impl() {
@@ -923,6 +933,7 @@ impl BluetoothMedia {
                    return;
                }
                self.phone_state_change("".into());
                self.uhid_send_hook_switch_status(&addr, false);

                // Try resume the A2DP stream (per MPS v1.0) on rejecting an incoming call or an
                // outgoing call is rejected.
@@ -1022,6 +1033,119 @@ impl BluetoothMedia {
        self.callbacks.lock().unwrap().remove_callback(id)
    }

    fn uhid_create(&mut self, addr: RawAddress) {
        debug!(
            "[{}]: UHID create: PhoneOpsEnabled {}",
            DisplayAddress(&addr),
            self.phone_ops_enabled,
        );
        // To change the value of phone_ops_enabled, you need to toggle the BluetoothFlossTelephony feature flag on chrome://flags.
        if !self.phone_ops_enabled {
            return;
        }
        if self.uhid.contains_key(&addr) {
            warn!("[{}]: UHID create: entry already created", DisplayAddress(&addr));
            return;
        }
        let adapter_addr = match &self.adapter {
            Some(adapter) => adapter.lock().unwrap().get_address().to_lowercase(),
            _ => "".to_string(),
        };
        let txl = self.tx.clone();
        let remote_addr = addr.to_string();
        self.uhid.insert(
            addr,
            UHidHfp::create(
                adapter_addr,
                addr.to_string(),
                self.adapter_get_remote_name(addr),
                move |m| {
                    match m {
                        OutputEvent::Close => debug!("UHID: Close"),
                        OutputEvent::Open => debug!("UHID: Open"),
                        OutputEvent::Output { data } => {
                            txl.blocking_send(Message::UHidHfpOutputCallback(
                                remote_addr.clone(),
                                data[0],
                                data[1],
                            ))
                            .unwrap();
                        }
                        _ => (),
                    };
                },
            ),
        );
    }

    fn uhid_destroy(&mut self, addr: &RawAddress) {
        if let Some(uhid) = self.uhid.get_mut(addr) {
            debug!("[{}]: UHID destroy", DisplayAddress(&addr));
            match uhid.destroy() {
                Err(e) => log::error!(
                    "[{}]: UHID destroy: Fail to destroy uhid {}",
                    DisplayAddress(&addr),
                    e
                ),
                Ok(_) => (),
            };
            self.uhid.remove(addr);
        } else {
            debug!("[{}]: UHID destroy: not a UHID device", DisplayAddress(&addr));
        }
    }

    fn uhid_send_hook_switch_status(&mut self, addr: &RawAddress, status: bool) {
        // To change the value of phone_ops_enabled, you need to toggle the BluetoothFlossTelephony feature flag on chrome://flags.
        if !self.phone_ops_enabled {
            return;
        }
        if let Some(uhid) = self.uhid.get_mut(addr) {
            debug!("[{}]: UHID: Send 'Hook Switch': {}", DisplayAddress(&addr), status);
            match uhid.send_input(status) {
                Err(e) => log::error!(
                    "[{}]: UHID: Fail to send 'Hook Switch={}' to uhid: {}",
                    DisplayAddress(&addr),
                    status,
                    e
                ),
                Ok(_) => (),
            };
        };
    }

    pub fn dispatch_uhid_hfp_output_callback(&mut self, address: String, id: u8, data: u8) {
        let addr = match RawAddress::from_string(address.clone()) {
            None => {
                warn!("UHID: Invalid device address for dispatch_uhid_hfp_output_callback");
                return;
            }
            Some(addr) => addr,
        };

        debug!(
            "[{}]: UHID: Received output report: id {}, data {}",
            DisplayAddress(&addr),
            id,
            data
        );

        if id == BLUETOOTH_TELEPHONY_UHID_REPORT_ID {
            if data == UHID_OUTPUT_NONE {
                self.hangup_call();
            } else if data == UHID_OUTPUT_RING {
                self.incoming_call("".into());
            } else if data == UHID_OUTPUT_OFF_HOOK {
                if self.phone_state.num_active > 0 {
                    return;
                }
                self.dialing_call("".into());
                self.answer_call();
                self.uhid_send_hook_switch_status(&addr, true);
            }
        }
    }

    fn notify_critical_profile_disconnected(&mut self, addr: RawAddress) {
        info!(
            "[{}]: Device connection state: {:?}.",
+11 −0
Original line number Diff line number Diff line
@@ -148,6 +148,9 @@ pub enum Message {
    QaGetHidReport(String, BthhReportType, u8),
    QaSetHidReport(String, BthhReportType, String),
    QaSendHidData(String, String),

    // UHid callbacks
    UHidHfpOutputCallback(String, u8, u8),
}

/// Represents suspend mode of a module.
@@ -424,6 +427,14 @@ impl Stack {
                    let status = bluetooth.lock().unwrap().send_hid_data_internal(addr, data);
                    bluetooth_qa.lock().unwrap().on_send_hid_data_completed(status);
                }

                // UHid callbacks
                Message::UHidHfpOutputCallback(addr, id, data) => {
                    bluetooth_media
                        .lock()
                        .unwrap()
                        .dispatch_uhid_hfp_output_callback(addr, id, data);
                }
            }
        }
    }
+1 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ nix = "0.23"
num-derive = "0.3"
num-traits = "0.2"
uhid-virt = "0.0.6"
tokio = { version = "1", features = ['rt'] }

[lib]
crate-type = ["rlib"]
+1 −0
Original line number Diff line number Diff line
@@ -6,4 +6,5 @@ pub mod at_command_parser;
pub mod cod;
pub mod socket;
pub mod uhid;
pub mod uhid_hfp;
pub mod uinput;
+157 −0
Original line number Diff line number Diff line
//! This library provides UHID for HFP to interact with WebHID.

use bt_topshim::topstack;
use log::debug;
use std::convert::TryFrom;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
pub use uhid_virt::OutputEvent;
use uhid_virt::{Bus, CreateParams, InputEvent, StreamError, UHID_EVENT_SIZE};

pub const BLUETOOTH_TELEPHONY_UHID_REPORT_ID: u8 = 1;
pub const UHID_OUTPUT_NONE: u8 = 0;
pub const UHID_OUTPUT_RING: u8 = 1;
pub const UHID_OUTPUT_OFF_HOOK: u8 = 2;

const RDESC: [u8; 51] = [
    0x05,
    0x0B, // Usage Page (Telephony)
    0x09,
    0x05, // Usage (Headset)
    0xA1,
    0x01, // Collection (Application)
    0x85,
    BLUETOOTH_TELEPHONY_UHID_REPORT_ID, //   Report ID (1)
    0x05,
    0x0B, //   Usage Page (Telephony)
    0x15,
    0x00, //   Logical Minimum (0)
    0x25,
    0x01, //   Logical Maximum (1)
    0x09,
    0x20, //   Usage (Hook Switch)
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x01, //   Report Count (1)
    0x81,
    0x23, //   Input
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x07, //   Report Count (7)
    0x81,
    0x01, //   Input
    0x05,
    0x08, //   Usage Page (LEDs)
    0x15,
    0x00, //   Logical Minimum (0)
    0x25,
    0x01, //   Logical Maximum (1)
    0x09,
    0x18, //   Usage (Ring)
    0x09,
    0x17, //   Usage (Off-Hook)
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x02, //   Report Count (2)
    0x91,
    0x22, //   Output
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x06, //   Report Count (6)
    0x91,
    0x01, //   Output
    0xC0, // End Collection
];

pub struct UHidHfp {
    handle: File,
}

impl UHidHfp {
    pub fn create<F>(
        adapter_addr: String,
        remote_addr: String,
        remote_name: String,
        output_callback: F,
    ) -> UHidHfp
    where
        F: Fn(OutputEvent) + std::marker::Send + 'static,
    {
        let rd_data = RDESC.to_vec();
        let create_params = CreateParams {
            name: remote_name,
            phys: adapter_addr,
            uniq: remote_addr.clone(),
            bus: Bus::BLUETOOTH,
            vendor: 0,
            product: 0,
            version: 0,
            country: 0,
            rd_data,
        };

        let create_event: [u8; UHID_EVENT_SIZE] = InputEvent::Create(create_params).into();
        let mut options = OpenOptions::new();
        options.read(true);
        options.write(true);
        if cfg!(unix) {
            options.custom_flags(libc::O_RDWR | libc::O_CLOEXEC);
        }
        let mut uhid_writer = options.open(Path::new("/dev/uhid")).unwrap();
        let mut uhid_reader = uhid_writer.try_clone().unwrap();
        uhid_writer.write_all(&create_event).unwrap();

        topstack::get_runtime().spawn_blocking(move || {
            let mut event = [0u8; UHID_EVENT_SIZE];
            debug!("UHID: reading loop start");
            loop {
                match uhid_reader.read_exact(&mut event) {
                    Err(e) => {
                        log::error!("UHID: Read error: {:?}", e);
                        break;
                    }
                    Ok(_) => (),
                }
                match OutputEvent::try_from(event) {
                    Ok(m) => {
                        match m {
                            OutputEvent::Stop => break,
                            _ => (),
                        };

                        output_callback(m);
                    }
                    Err(e) => {
                        match e {
                            StreamError::Io(e) => log::error!("UHID: IO error: {}", e),
                            StreamError::UnknownEventType(e) => {
                                log::error!("UHID: Unknown event type: {}", e)
                            }
                        }
                        break;
                    }
                }
            }
            debug!("UHID: reading loop completed");
        });

        UHidHfp { handle: uhid_writer }
    }

    pub fn destroy(&mut self) -> io::Result<()> {
        let destroy_event: [u8; UHID_EVENT_SIZE] = InputEvent::Destroy.into();
        self.handle.write_all(&destroy_event)
    }

    pub fn send_input(&mut self, status: bool) -> io::Result<()> {
        let data: [u8; 2] = [BLUETOOTH_TELEPHONY_UHID_REPORT_ID, status.into()];
        let input_event: [u8; UHID_EVENT_SIZE] = InputEvent::Input { data: &data }.into();
        self.handle.write_all(&input_event)
    }
}