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

Commit 8cc7ee12 authored by Jeremy Wu's avatar Jeremy Wu
Browse files

Floss: wait when audio profile connection received but SDP is empty

In some rare cases, we could be receiving audio profile connections
while the headset has not reported its available set of audio profiles.
This may cause some profiles to be missed in propagating to the audio
server because an empty set of available profiles implies all profiles
have been connected.

In this CL, building on the observation that in such cases the headset
will eventually initiate all desired connections, we will wait for 6s
of timeout (or if both A2DP and HFP are connected) before retrying
and 10s (in total) before propagating the device to the audio server.

Bug: 289015291
Tag: #floss
Test: atest bluetooth_test_gd
Test: manually pair/forget/connect/disconnect airpods/XM3/4/5
Change-Id: If4bc92f9525099ce9c5bfc85c5a5b4fdf4905c11
parent 2f4a9664
Loading
Loading
Loading
Loading
+58 −18
Original line number Diff line number Diff line
@@ -43,8 +43,9 @@ use crate::uuid::Profile;
use crate::{Message, RPCProxy};

// The timeout we have to wait for all supported profiles to connect after we
// receive the first profile connected event. The host shall disconnect the
// device after this many seconds of timeout.
// receive the first profile connected event. The host shall disconnect or
// force connect the potentially partially connected device after this many
// seconds of timeout.
const PROFILE_DISCOVERY_TIMEOUT_SEC: u64 = 10;
// The timeout we have to wait for the initiator peer device to complete the
// initial profile connection. After this many seconds, we will begin to
@@ -256,6 +257,7 @@ enum DeviceConnectionStates {
    ConnectingAfterRetry,  // Host initiated requests to missing profiles after timeout
    FullyConnected,        // All profiles (excluding AVRCP) are connected
    Disconnecting,         // Working towards disconnection of each connected profile
    WaitingConnection,     // Waiting for new connections initiated by peer
}

pub struct BluetoothMedia {
@@ -605,7 +607,8 @@ impl BluetoothMedia {
                    info!("[{}]: state {:?}", DisplayAddress(&addr), state);
                    match state {
                        DeviceConnectionStates::ConnectingBeforeRetry
                        | DeviceConnectionStates::ConnectingAfterRetry => {
                        | DeviceConnectionStates::ConnectingAfterRetry
                        | DeviceConnectionStates::WaitingConnection => {
                            self.delay_volume_update.insert(Profile::AvrcpController, volume);
                        }
                        DeviceConnectionStates::FullyConnected => {
@@ -779,7 +782,8 @@ impl BluetoothMedia {
                );
                match states.get(&addr).unwrap() {
                    DeviceConnectionStates::ConnectingBeforeRetry
                    | DeviceConnectionStates::ConnectingAfterRetry => {
                    | DeviceConnectionStates::ConnectingAfterRetry
                    | DeviceConnectionStates::WaitingConnection => {
                        self.delay_volume_update.insert(Profile::Hfp, volume);
                    }
                    DeviceConnectionStates::FullyConnected => {
@@ -1122,7 +1126,7 @@ impl BluetoothMedia {
        first_conn_ts: Instant,
    ) {
        let now_ts = Instant::now();
        let total_duration = Duration::from_secs(CONNECT_MISSING_PROFILES_TIMEOUT_SEC);
        let total_duration = Duration::from_secs(PROFILE_DISCOVERY_TIMEOUT_SEC);
        let sleep_duration = (first_conn_ts + total_duration).saturating_duration_since(now_ts);
        sleep(sleep_duration).await;
        let _ = txl.send(Message::Media(MediaActions::ForceEnterConnected(addr.to_string()))).await;
@@ -1193,6 +1197,33 @@ impl BluetoothMedia {
        if states.get(&addr).is_none() {
            states.insert(addr, DeviceConnectionStates::ConnectingBeforeRetry);
        }

        if states.get(&addr).unwrap() != &DeviceConnectionStates::FullyConnected {
            if available_profiles.is_empty() {
                // Some headsets may start initiating connections to audio profiles before they are
                // exposed to the stack. In this case, wait for either all critical profiles have been
                // connected or some timeout to enter the |FullyConnected| state.
                if connected_profiles.contains(&Profile::Hfp)
                    && connected_profiles.contains(&Profile::A2dpSink)
                {
                    info!(
                        "[{}]: Fully connected, available profiles: {:?}, connected profiles: {:?}.",
                        DisplayAddress(&addr),
                        available_profiles,
                        connected_profiles
                    );

                    states.insert(addr, DeviceConnectionStates::FullyConnected);
                } else {
                    warn!(
                        "[{}]: Connected profiles: {:?}, waiting for peer to initiate remaining connections.",
                        DisplayAddress(&addr),
                        connected_profiles
                    );

                    states.insert(addr, DeviceConnectionStates::WaitingConnection);
                }
            } else {
                if missing_profiles.is_empty()
                    || missing_profiles == HashSet::from([Profile::AvrcpController])
                {
@@ -1205,6 +1236,8 @@ impl BluetoothMedia {

                    states.insert(addr, DeviceConnectionStates::FullyConnected);
                }
            }
        }

        info!(
            "[{}]: Device connection state: {:?}.",
@@ -1300,6 +1333,13 @@ impl BluetoothMedia {
                guard.insert(addr, None);
            }
            DeviceConnectionStates::Disconnecting => {}
            DeviceConnectionStates::WaitingConnection => {
                let task = topstack::get_runtime().spawn(async move {
                    BluetoothMedia::wait_retry(&tasks, &device_states, &txl, &addr, ts).await;
                    BluetoothMedia::wait_force_enter_connected(&txl, &addr, ts).await;
                });
                guard.insert(addr, Some((task, ts)));
            }
        }
    }

@@ -1716,9 +1756,9 @@ impl BluetoothMedia {
    }

    // Force the media enters the FullyConnected state and then triggers a retry.
    // This function is only used for qualification as a replacement of normal retry.
    // Usually PTS initiates the connection of the necessary profiles, and Floss should notify
    // CRAS of the new audio device regardless of the unconnected profiles.
    // When this function is used for qualification as a replacement of normal retry,
    // PTS could initiate the connection of the necessary profiles, and Floss should
    // notify CRAS of the new audio device regardless of the unconnected profiles.
    // Still retry in the end because some test cases require that.
    fn force_enter_connected(&mut self, address: String) {
        let addr = match RawAddress::from_string(address.clone()) {