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

Commit 1cb90b0b authored by Frédéric Danis's avatar Frédéric Danis
Browse files

Floss: Add UHID Mute support

Bug: 277693919
Test: Conduct the following manual tests.
  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", "Ring" and "Mute" test cases are passed
  used https://online-voice-recorer.com during "Hook Switch" and
    "off-Hook" tests to check audio input
  Google Meet: headset is able to mute, un-mute and hang-up the call
Test: atest bluetooth_test_gd

Change-Id: I294bdfb9aa66169042502a057615572260cf86a8
parent 56edb4f1
Loading
Loading
Loading
Loading
+125 −35
Original line number Diff line number Diff line
@@ -21,8 +21,9 @@ 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,
    OutputEvent, UHidHfp, BLUETOOTH_TELEPHONY_UHID_REPORT_ID, UHID_INPUT_HOOK_SWITCH,
    UHID_INPUT_PHONE_MUTE, UHID_OUTPUT_MUTE, UHID_OUTPUT_NONE, UHID_OUTPUT_OFF_HOOK,
    UHID_OUTPUT_RING,
};
use bt_utils::uinput::UInput;

@@ -265,6 +266,12 @@ enum DeviceConnectionStates {
    Disconnecting,         // Working towards disconnection of each connected profile
}

struct UHid {
    pub handle: UHidHfp,
    pub volume: u8,
    pub muted: bool,
}

pub struct BluetoothMedia {
    intf: Arc<Mutex<BluetoothInterface>>,
    battery_provider_manager: Arc<Mutex<Box<BatteryProviderManager>>>,
@@ -298,7 +305,7 @@ pub struct BluetoothMedia {
    mps_qualification_enabled: bool,
    memory_dialing_number: Option<String>,
    last_dialing_number: Option<String>,
    uhid: HashMap<RawAddress, UHidHfp>,
    uhid: HashMap<RawAddress, UHid>,
}

impl BluetoothMedia {
@@ -816,6 +823,31 @@ impl BluetoothMedia {
                    _ => {}
                }
            }
            HfpCallbacks::MicVolumeUpdate(volume, addr) => {
                if !self.phone_ops_enabled {
                    return;
                }

                if self.hfp_states.get(&addr).is_none()
                    || BthfConnectionState::SlcConnected != *self.hfp_states.get(&addr).unwrap()
                {
                    warn!("[{}]: Unknown address hfp or slc not ready", addr.to_string());
                    return;
                }

                if let Some(uhid) = self.uhid.get_mut(&addr) {
                    if volume == 0 && !uhid.muted {
                        uhid.muted = true;
                        self.uhid_send_input_report(&addr);
                    } else if volume > 0 {
                        uhid.volume = volume;
                        if uhid.muted {
                            uhid.muted = false;
                            self.uhid_send_input_report(&addr);
                        }
                    }
                }
            }
            HfpCallbacks::VendorSpecificAtCommand(at_string, addr) => {
                let at_command = match parse_at_command_data(at_string) {
                    Ok(command) => command,
@@ -942,7 +974,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);
                self.uhid_send_input_report(&addr);
            }
            HfpCallbacks::HangupCall(addr) => {
                if !self.hangup_call_impl() {
@@ -950,7 +982,7 @@ impl BluetoothMedia {
                    return;
                }
                self.phone_state_change("".into());
                self.uhid_send_hook_switch_status(&addr, false);
                self.uhid_send_input_report(&addr);

                // Try resume the A2DP stream (per MPS v1.0) on rejecting an incoming call or an
                // outgoing call is rejected.
@@ -1072,7 +1104,8 @@ impl BluetoothMedia {
        let remote_addr = addr.to_string();
        self.uhid.insert(
            addr,
            UHidHfp::create(
            UHid {
                handle: UHidHfp::create(
                    adapter_addr,
                    addr.to_string(),
                    self.adapter_get_remote_name(addr),
@@ -1092,13 +1125,16 @@ impl BluetoothMedia {
                        };
                    },
                ),
                volume: 15, // By default use maximum volume in case microphone gain has not been received
                muted: false,
            },
        );
    }

    fn uhid_destroy(&mut self, addr: &RawAddress) {
        if let Some(uhid) = self.uhid.get_mut(addr) {
            debug!("[{}]: UHID destroy", DisplayAddress(&addr));
            match uhid.destroy() {
            match uhid.handle.destroy() {
                Err(e) => log::error!(
                    "[{}]: UHID destroy: Fail to destroy uhid {}",
                    DisplayAddress(&addr),
@@ -1112,18 +1148,25 @@ impl BluetoothMedia {
        }
    }

    fn uhid_send_hook_switch_status(&mut self, addr: &RawAddress, status: bool) {
    fn uhid_send_input_report(&mut self, addr: &RawAddress) {
        // 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) {
            let mut data = 0;
            if self.call_list.iter().any(|c| c.source == CallSource::HID) {
                data |= UHID_INPUT_HOOK_SWITCH;
            }
            if uhid.muted {
                data |= UHID_INPUT_PHONE_MUTE;
            }
            debug!("[{}]: UHID: Send input report: {}", DisplayAddress(&addr), data);
            match uhid.handle.send_input(data) {
                Err(e) => log::error!(
                    "[{}]: UHID: Fail to send 'Hook Switch={}' to uhid: {}",
                    "[{}]: UHID: Fail to send Input Report ({}) to uhid: {}",
                    DisplayAddress(&addr),
                    status,
                    data,
                    e
                ),
                Ok(_) => (),
@@ -1147,20 +1190,67 @@ impl BluetoothMedia {
            data
        );

        let uhid = match self.uhid.get_mut(&addr) {
            Some(uhid) => uhid,
            None => {
                warn!("[{}]: UHID: No valid UHID", DisplayAddress(&addr));
                return;
            }
        };

        if id == BLUETOOTH_TELEPHONY_UHID_REPORT_ID {
            if data == UHID_OUTPUT_NONE {
            let mute = data & UHID_OUTPUT_MUTE;
            if mute == UHID_OUTPUT_MUTE && !uhid.muted {
                uhid.muted = true;
                self.set_hfp_mic_volume(0, addr);
            } else if mute != UHID_OUTPUT_MUTE && uhid.muted {
                uhid.muted = false;
                let saved_volume = uhid.volume;
                self.set_hfp_mic_volume(saved_volume, addr);
            }

            let call_state = data & (UHID_OUTPUT_RING | UHID_OUTPUT_OFF_HOOK);
            if call_state == UHID_OUTPUT_NONE {
                self.hangup_call();
            } else if data == UHID_OUTPUT_RING {
            } else if call_state == UHID_OUTPUT_RING {
                self.incoming_call("".into());
            } else if data == UHID_OUTPUT_OFF_HOOK {
            } else if call_state == UHID_OUTPUT_OFF_HOOK {
                if self.call_list.iter().any(|c| c.source == CallSource::HID) {
                    return;
                }
                self.dialing_call("".into());
                self.answer_call();
                self.uhid_send_hook_switch_status(&addr, true);
                self.uhid_send_input_report(&addr);
            }
        }
    }

    fn set_hfp_mic_volume(&mut self, volume: u8, addr: RawAddress) {
        let vol = match i8::try_from(volume) {
            Ok(val) if val <= 15 => val,
            _ => {
                warn!("[{}]: Ignore invalid mic volume {}", DisplayAddress(&addr), volume);
                return;
            }
        };

        if self.hfp_states.get(&addr).is_none() {
            warn!(
                "[{}]: Ignore mic volume event for unconnected or disconnected HFP device",
                DisplayAddress(&addr)
            );
            return;
        }

        match self.hfp.as_mut() {
            Some(hfp) => {
                let status = hfp.set_mic_volume(vol, addr);
                if status != BtStatus::Success {
                    warn!("[{}]: Failed to set mic volume to {}", DisplayAddress(&addr), vol);
                }
            }
            None => warn!("Uninitialized HFP to set mic volume"),
        };
    }

    fn notify_critical_profile_disconnected(&mut self, addr: RawAddress) {
+16 −9
Original line number Diff line number Diff line
@@ -11,11 +11,14 @@ 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_INPUT_HOOK_SWITCH: u8 = 1 << 0;
pub const UHID_INPUT_PHONE_MUTE: u8 = 1 << 1;
pub const UHID_OUTPUT_NONE: u8 = 0;
pub const UHID_OUTPUT_RING: u8 = 1;
pub const UHID_OUTPUT_OFF_HOOK: u8 = 2;
pub const UHID_OUTPUT_RING: u8 = 1 << 0;
pub const UHID_OUTPUT_OFF_HOOK: u8 = 1 << 1;
pub const UHID_OUTPUT_MUTE: u8 = 1 << 2;

const RDESC: [u8; 51] = [
const RDESC: [u8; 55] = [
    0x05,
    0x0B, // Usage Page (Telephony)
    0x09,
@@ -32,16 +35,18 @@ const RDESC: [u8; 51] = [
    0x01, //   Logical Maximum (1)
    0x09,
    0x20, //   Usage (Hook Switch)
    0x09,
    0x2f, //   Usage (Phone Mute)
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x01, //   Report Count (1)
    0x02, //   Report Count (2)
    0x81,
    0x23, //   Input
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x07, //   Report Count (7)
    0x06, //   Report Count (6)
    0x81,
    0x01, //   Input
    0x05,
@@ -54,16 +59,18 @@ const RDESC: [u8; 51] = [
    0x18, //   Usage (Ring)
    0x09,
    0x17, //   Usage (Off-Hook)
    0x09,
    0x09, //   Usage (Mute)
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x02, //   Report Count (2)
    0x03, //   Report Count (3)
    0x91,
    0x22, //   Output
    0x75,
    0x01, //   Report Size (1)
    0x95,
    0x06, //   Report Count (6)
    0x05, //   Report Count (5)
    0x91,
    0x01, //   Output
    0xC0, // End Collection
@@ -149,8 +156,8 @@ impl UHidHfp {
        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()];
    pub fn send_input(&mut self, report: u8) -> io::Result<()> {
        let data: [u8; 2] = [BLUETOOTH_TELEPHONY_UHID_REPORT_ID, report];
        let input_event: [u8; UHID_EVENT_SIZE] = InputEvent::Input { data: &data }.into();
        self.handle.write_all(&input_event)
    }
+18 −3
Original line number Diff line number Diff line
@@ -43,6 +43,10 @@ static void volume_update_cb(uint8_t volume, RawAddress* addr) {
  rusty::hfp_volume_update_callback(volume, *addr);
}

static void mic_volume_update_cb(uint8_t volume, RawAddress* addr) {
  rusty::hfp_mic_volume_update_callback(volume, *addr);
}

static void vendor_specific_at_command_cb(char* at_string, RawAddress* addr) {
  rusty::hfp_vendor_specific_at_command_callback(::rust::String{at_string}, *addr);
}
@@ -164,10 +168,17 @@ class DBusHeadsetCallbacks : public headset::Callbacks {
  }

  void VolumeControlCallback(headset::bthf_volume_type_t type, int volume, RawAddress* bd_addr) override {
    if (type != headset::bthf_volume_type_t::BTHF_VOLUME_TYPE_SPK || volume < 0) return;
    if (volume < 0) return;
    if (volume > 15) volume = 15;
    LOG_INFO("VolumeControlCallback %d from %s", volume, ADDRESS_TO_LOGGABLE_CSTR(*bd_addr));
    if (type == headset::bthf_volume_type_t::BTHF_VOLUME_TYPE_SPK) {
      LOG_INFO(
          "VolumeControlCallback (Spk) %d from %s", volume, ADDRESS_TO_LOGGABLE_CSTR(*bd_addr));
      topshim::rust::internal::volume_update_cb(volume, bd_addr);
    } else if (type == headset::bthf_volume_type_t::BTHF_VOLUME_TYPE_MIC) {
      LOG_INFO(
          "VolumeControlCallback (Mic) %d from %s", volume, ADDRESS_TO_LOGGABLE_CSTR(*bd_addr));
      topshim::rust::internal::mic_volume_update_cb(volume, bd_addr);
    }
  }

  void DialCallCallback(char* number, RawAddress* bd_addr) override {
@@ -326,6 +337,10 @@ int HfpIntf::set_volume(int8_t volume, RawAddress addr) {
  return intf_->VolumeControl(headset::bthf_volume_type_t::BTHF_VOLUME_TYPE_SPK, volume, &addr);
}

uint32_t HfpIntf::set_mic_volume(int8_t volume, RawAddress addr) {
  return intf_->VolumeControl(headset::bthf_volume_type_t::BTHF_VOLUME_TYPE_MIC, volume, &addr);
}

uint32_t HfpIntf::disconnect(RawAddress addr) {
  return intf_->Disconnect(&addr);
}
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ class HfpIntf {
  int connect_audio(RawAddress addr, bool sco_offload, int disabled_codecs);
  int set_active_device(RawAddress addr);
  int set_volume(int8_t volume, RawAddress addr);
  uint32_t set_mic_volume(int8_t volume, RawAddress addr);
  uint32_t disconnect(RawAddress addr);
  int disconnect_audio(RawAddress addr);
  uint32_t device_status_notification(TelephonyDeviceStatus status, RawAddress addr);
+13 −0
Original line number Diff line number Diff line
@@ -154,6 +154,7 @@ pub mod ffi {
        ) -> i32;
        fn set_active_device(self: Pin<&mut HfpIntf>, bt_addr: RawAddress) -> i32;
        fn set_volume(self: Pin<&mut HfpIntf>, volume: i8, bt_addr: RawAddress) -> i32;
        fn set_mic_volume(self: Pin<&mut HfpIntf>, volume: i8, bt_addr: RawAddress) -> u32;
        fn disconnect(self: Pin<&mut HfpIntf>, bt_addr: RawAddress) -> u32;
        fn disconnect_audio(self: Pin<&mut HfpIntf>, bt_addr: RawAddress) -> i32;
        fn device_status_notification(
@@ -187,6 +188,7 @@ pub mod ffi {
        fn hfp_connection_state_callback(state: u32, addr: RawAddress);
        fn hfp_audio_state_callback(state: u32, addr: RawAddress);
        fn hfp_volume_update_callback(volume: u8, addr: RawAddress);
        fn hfp_mic_volume_update_callback(volume: u8, addr: RawAddress);
        fn hfp_vendor_specific_at_command_callback(at_string: String, addr: RawAddress);
        fn hfp_battery_level_update_callback(battery_level: u8, addr: RawAddress);
        fn hfp_wbs_caps_update_callback(wbs_supported: bool, addr: RawAddress);
@@ -234,6 +236,7 @@ pub enum HfpCallbacks {
    ConnectionState(BthfConnectionState, RawAddress),
    AudioState(BthfAudioState, RawAddress),
    VolumeUpdate(u8, RawAddress),
    MicVolumeUpdate(u8, RawAddress),
    VendorSpecificAtCommand(String, RawAddress),
    BatteryLevelUpdate(u8, RawAddress),
    WbsCapsUpdate(bool, RawAddress),
@@ -268,6 +271,11 @@ cb_variant!(
    hfp_volume_update_callback -> HfpCallbacks::VolumeUpdate,
    u8, RawAddress);

cb_variant!(
    HfpCb,
    hfp_mic_volume_update_callback -> HfpCallbacks::MicVolumeUpdate,
    u8, RawAddress);

cb_variant!(
    HfpCb,
    hfp_vendor_specific_at_command_callback -> HfpCallbacks::VendorSpecificAtCommand,
@@ -398,6 +406,11 @@ impl Hfp {
        self.internal.pin_mut().set_volume(volume, addr)
    }

    #[profile_enabled_or(BtStatus::NotReady.into())]
    pub fn set_mic_volume(&mut self, volume: i8, addr: RawAddress) -> BtStatus {
        BtStatus::from(self.internal.pin_mut().set_mic_volume(volume, addr))
    }

    #[profile_enabled_or(BtStatus::NotReady)]
    pub fn disconnect(&mut self, addr: RawAddress) -> BtStatus {
        BtStatus::from(self.internal.pin_mut().disconnect(addr))