Loading system/gd/rust/linux/stack/src/bluetooth_media.rs +76 −3 Original line number Diff line number Diff line Loading @@ -218,6 +218,7 @@ impl BluetoothAudioDevice { pub enum MediaActions { Connect(String), Disconnect(String), ForceEnterConnected(String), // Only used for qualification. } #[derive(Debug, Clone, PartialEq)] Loading Loading @@ -343,7 +344,7 @@ impl BluetoothMedia { self.connected_profiles.entry(addr).or_insert_with(HashSet::new).remove(&profile); if is_profile_critical { if is_profile_critical && self.is_complete_profiles_required() { self.notify_critical_profile_disconnected(addr); } Loading Loading @@ -452,8 +453,10 @@ impl BluetoothMedia { self.a2dp_caps.remove(&addr); self.a2dp_audio_state.remove(&addr); self.rm_connected_profile(addr, uuid::Profile::A2dpSink, true); if self.is_complete_profiles_required() { self.disconnect(addr.to_string()); } } _ => { self.a2dp_states.insert(addr, state); } Loading Loading @@ -581,6 +584,7 @@ impl BluetoothMedia { match action { MediaActions::Connect(address) => self.connect(address), MediaActions::Disconnect(address) => self.disconnect(address), MediaActions::ForceEnterConnected(address) => self.force_enter_connected(address), } } Loading Loading @@ -624,8 +628,10 @@ impl BluetoothMedia { self.hfp_cap.remove(&addr); self.hfp_audio_state.remove(&addr); self.rm_connected_profile(addr, uuid::Profile::Hfp, true); if self.is_complete_profiles_required() { self.disconnect(addr.to_string()); } } BthfConnectionState::Connecting => { info!("[{}]: hfp connecting.", DisplayAddress(&addr)); } Loading Loading @@ -915,6 +921,18 @@ impl BluetoothMedia { let _ = txl.send(Message::Media(MediaActions::Disconnect(addr.to_string()))).await; } async fn wait_force_enter_connected( txl: &Sender<Message>, addr: &RawAddress, first_conn_ts: Instant, ) { let now_ts = Instant::now(); let total_duration = Duration::from_secs(CONNECT_MISSING_PROFILES_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; } fn notify_media_capability_updated(&mut self, addr: RawAddress) { let mut guard = self.fallback_tasks.lock().unwrap(); let mut states = self.device_states.lock().unwrap(); Loading Loading @@ -980,11 +998,16 @@ impl BluetoothMedia { let device_states = self.device_states.clone(); let txl = self.tx.clone(); let ts = first_conn_ts; let is_complete_profiles_required = self.is_complete_profiles_required(); match states.get(&addr).unwrap() { DeviceConnectionStates::Initiating => { let task = topstack::get_runtime().spawn(async move { // As initiator we can just immediately start connecting let _ = txl.send(Message::Media(MediaActions::Connect(addr.to_string()))).await; if !is_complete_profiles_required { BluetoothMedia::wait_force_enter_connected(&txl, &addr, ts).await; return; } BluetoothMedia::wait_retry(&tasks, &device_states, &txl, &addr, ts).await; BluetoothMedia::wait_disconnect(&tasks, &device_states, &txl, &addr, ts).await; }); Loading @@ -992,6 +1015,10 @@ impl BluetoothMedia { } DeviceConnectionStates::ConnectingBeforeRetry => { let task = topstack::get_runtime().spawn(async move { if !is_complete_profiles_required { BluetoothMedia::wait_force_enter_connected(&txl, &addr, ts).await; return; } BluetoothMedia::wait_retry(&tasks, &device_states, &txl, &addr, ts).await; BluetoothMedia::wait_disconnect(&tasks, &device_states, &txl, &addr, ts).await; }); Loading @@ -999,6 +1026,10 @@ impl BluetoothMedia { } DeviceConnectionStates::ConnectingAfterRetry => { let task = topstack::get_runtime().spawn(async move { if !is_complete_profiles_required { BluetoothMedia::wait_force_enter_connected(&txl, &addr, ts).await; return; } BluetoothMedia::wait_disconnect(&tasks, &device_states, &txl, &addr, ts).await; }); guard.insert(addr, Some((task, ts))); Loading Loading @@ -1402,6 +1433,34 @@ impl BluetoothMedia { } true } // Per MPS v1.0 (Multi-Profile Specification), disconnecting or failing to connect // a profile should not affect the others. // Allow partial profiles connection during qualification (phone operations are enabled). fn is_complete_profiles_required(&self) -> bool { !self.phone_ops_enabled } // 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. // 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()) { None => { warn!("Invalid device address for force_enter_connected"); return; } Some(addr) => addr, }; self.device_states .lock() .unwrap() .insert(addr.clone(), DeviceConnectionStates::FullyConnected); self.notify_media_capability_updated(addr); self.connect(address); } } fn get_a2dp_dispatcher(tx: Sender<Message>) -> A2dpCallbacksDispatcher { Loading Loading @@ -1777,6 +1836,14 @@ impl IBluetoothMedia for BluetoothMedia { } fn set_active_device(&mut self, address: String) { // During MPS tests, there might be some A2DP stream manipulation unexpected to CRAS. // CRAS would then attempt to reset the active device. Ignore it during test. // TODO(b/265988575): CRAS is migrating to use ResetActiveDevice instead. Remove this // after the migration is done. if !self.is_complete_profiles_required() && address == String::from("00:00:00:00:00:00") { return; } let addr = match RawAddress::from_string(address.clone()) { None => { warn!("Invalid device address for set_active_device"); Loading @@ -1799,6 +1866,12 @@ impl IBluetoothMedia for BluetoothMedia { } fn reset_active_device(&mut self) { // During MPS tests, there might be some A2DP stream manipulation unexpected to CRAS. // CRAS would then attempt to reset the active device. Ignore it during test. if !self.is_complete_profiles_required() { return; } if let Some(a2dp) = self.a2dp.as_mut() { a2dp.set_active_device(RawAddress::empty()); } else { Loading Loading
system/gd/rust/linux/stack/src/bluetooth_media.rs +76 −3 Original line number Diff line number Diff line Loading @@ -218,6 +218,7 @@ impl BluetoothAudioDevice { pub enum MediaActions { Connect(String), Disconnect(String), ForceEnterConnected(String), // Only used for qualification. } #[derive(Debug, Clone, PartialEq)] Loading Loading @@ -343,7 +344,7 @@ impl BluetoothMedia { self.connected_profiles.entry(addr).or_insert_with(HashSet::new).remove(&profile); if is_profile_critical { if is_profile_critical && self.is_complete_profiles_required() { self.notify_critical_profile_disconnected(addr); } Loading Loading @@ -452,8 +453,10 @@ impl BluetoothMedia { self.a2dp_caps.remove(&addr); self.a2dp_audio_state.remove(&addr); self.rm_connected_profile(addr, uuid::Profile::A2dpSink, true); if self.is_complete_profiles_required() { self.disconnect(addr.to_string()); } } _ => { self.a2dp_states.insert(addr, state); } Loading Loading @@ -581,6 +584,7 @@ impl BluetoothMedia { match action { MediaActions::Connect(address) => self.connect(address), MediaActions::Disconnect(address) => self.disconnect(address), MediaActions::ForceEnterConnected(address) => self.force_enter_connected(address), } } Loading Loading @@ -624,8 +628,10 @@ impl BluetoothMedia { self.hfp_cap.remove(&addr); self.hfp_audio_state.remove(&addr); self.rm_connected_profile(addr, uuid::Profile::Hfp, true); if self.is_complete_profiles_required() { self.disconnect(addr.to_string()); } } BthfConnectionState::Connecting => { info!("[{}]: hfp connecting.", DisplayAddress(&addr)); } Loading Loading @@ -915,6 +921,18 @@ impl BluetoothMedia { let _ = txl.send(Message::Media(MediaActions::Disconnect(addr.to_string()))).await; } async fn wait_force_enter_connected( txl: &Sender<Message>, addr: &RawAddress, first_conn_ts: Instant, ) { let now_ts = Instant::now(); let total_duration = Duration::from_secs(CONNECT_MISSING_PROFILES_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; } fn notify_media_capability_updated(&mut self, addr: RawAddress) { let mut guard = self.fallback_tasks.lock().unwrap(); let mut states = self.device_states.lock().unwrap(); Loading Loading @@ -980,11 +998,16 @@ impl BluetoothMedia { let device_states = self.device_states.clone(); let txl = self.tx.clone(); let ts = first_conn_ts; let is_complete_profiles_required = self.is_complete_profiles_required(); match states.get(&addr).unwrap() { DeviceConnectionStates::Initiating => { let task = topstack::get_runtime().spawn(async move { // As initiator we can just immediately start connecting let _ = txl.send(Message::Media(MediaActions::Connect(addr.to_string()))).await; if !is_complete_profiles_required { BluetoothMedia::wait_force_enter_connected(&txl, &addr, ts).await; return; } BluetoothMedia::wait_retry(&tasks, &device_states, &txl, &addr, ts).await; BluetoothMedia::wait_disconnect(&tasks, &device_states, &txl, &addr, ts).await; }); Loading @@ -992,6 +1015,10 @@ impl BluetoothMedia { } DeviceConnectionStates::ConnectingBeforeRetry => { let task = topstack::get_runtime().spawn(async move { if !is_complete_profiles_required { BluetoothMedia::wait_force_enter_connected(&txl, &addr, ts).await; return; } BluetoothMedia::wait_retry(&tasks, &device_states, &txl, &addr, ts).await; BluetoothMedia::wait_disconnect(&tasks, &device_states, &txl, &addr, ts).await; }); Loading @@ -999,6 +1026,10 @@ impl BluetoothMedia { } DeviceConnectionStates::ConnectingAfterRetry => { let task = topstack::get_runtime().spawn(async move { if !is_complete_profiles_required { BluetoothMedia::wait_force_enter_connected(&txl, &addr, ts).await; return; } BluetoothMedia::wait_disconnect(&tasks, &device_states, &txl, &addr, ts).await; }); guard.insert(addr, Some((task, ts))); Loading Loading @@ -1402,6 +1433,34 @@ impl BluetoothMedia { } true } // Per MPS v1.0 (Multi-Profile Specification), disconnecting or failing to connect // a profile should not affect the others. // Allow partial profiles connection during qualification (phone operations are enabled). fn is_complete_profiles_required(&self) -> bool { !self.phone_ops_enabled } // 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. // 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()) { None => { warn!("Invalid device address for force_enter_connected"); return; } Some(addr) => addr, }; self.device_states .lock() .unwrap() .insert(addr.clone(), DeviceConnectionStates::FullyConnected); self.notify_media_capability_updated(addr); self.connect(address); } } fn get_a2dp_dispatcher(tx: Sender<Message>) -> A2dpCallbacksDispatcher { Loading Loading @@ -1777,6 +1836,14 @@ impl IBluetoothMedia for BluetoothMedia { } fn set_active_device(&mut self, address: String) { // During MPS tests, there might be some A2DP stream manipulation unexpected to CRAS. // CRAS would then attempt to reset the active device. Ignore it during test. // TODO(b/265988575): CRAS is migrating to use ResetActiveDevice instead. Remove this // after the migration is done. if !self.is_complete_profiles_required() && address == String::from("00:00:00:00:00:00") { return; } let addr = match RawAddress::from_string(address.clone()) { None => { warn!("Invalid device address for set_active_device"); Loading @@ -1799,6 +1866,12 @@ impl IBluetoothMedia for BluetoothMedia { } fn reset_active_device(&mut self) { // During MPS tests, there might be some A2DP stream manipulation unexpected to CRAS. // CRAS would then attempt to reset the active device. Ignore it during test. if !self.is_complete_profiles_required() { return; } if let Some(a2dp) = self.a2dp.as_mut() { a2dp.set_active_device(RawAddress::empty()); } else { Loading