audio: Remove notification if hot plugged device is activated manually

Handles the situation when a hotplugged device which triggers the
notification has been activated by users via settings or quick settings,
rather than via the switch button on notification body.

Bug: b/328425880
Test: CrasAudioHandlerTest
Change-Id: I9eca12073022d9b102c4301171fb550d2efde216
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5606668
Reviewed-by: Longbo Wei <longbowei@google.com>
Commit-Queue: Wenyu Zhang <zhangwenyu@google.com>
Cr-Commit-Position: refs/heads/main@{#1312120}
diff --git a/chromeos/ash/components/audio/audio_selection_notification_handler.cc b/chromeos/ash/components/audio/audio_selection_notification_handler.cc
index 9955cc4..b8768c0 100644
--- a/chromeos/ash/components/audio/audio_selection_notification_handler.cc
+++ b/chromeos/ash/components/audio/audio_selection_notification_handler.cc
@@ -392,11 +392,11 @@
 }
 
 void AudioSelectionNotificationHandler::RemoveNotificationIfNecessary(
-    const AudioDeviceList& removed_devices) {
-  const AudioDeviceList& hotplug_devices = removed_devices.front().is_input
-                                               ? hotplug_input_devices_
-                                               : hotplug_output_devices_;
-  for (const AudioDevice& device : removed_devices) {
+    const AudioDeviceList& removed_or_activated_devices) {
+  const AudioDeviceList& hotplug_devices =
+      removed_or_activated_devices.front().is_input ? hotplug_input_devices_
+                                                    : hotplug_output_devices_;
+  for (const AudioDevice& device : removed_or_activated_devices) {
     if (IsDeviceInList(device, hotplug_devices)) {
       // Remove notification and hotplug_input/output_devices_.
       auto* message_center = message_center::MessageCenter::Get();
diff --git a/chromeos/ash/components/audio/audio_selection_notification_handler.h b/chromeos/ash/components/audio/audio_selection_notification_handler.h
index 1bd5dff..da62555c2 100644
--- a/chromeos/ash/components/audio/audio_selection_notification_handler.h
+++ b/chromeos/ash/components/audio/audio_selection_notification_handler.h
@@ -99,8 +99,11 @@
       OpenSettingsAudioPageCallback open_settings_audio_page_callback);
 
   // Handles the situation when a hotplugged device which triggers the
-  // notification has been removed. Remove the notification in this case.
-  void RemoveNotificationIfNecessary(const AudioDeviceList& removed_devices);
+  // notification has been removed, or has been activated by users via settings
+  // or quick settings, rather than via the switch button on notification body.
+  // Remove the notification in these cases.
+  void RemoveNotificationIfNecessary(
+      const AudioDeviceList& removed_or_activated_devices);
 
  private:
   // Grant friend access for comprehensive testing of private/protected members.
diff --git a/chromeos/ash/components/audio/cras_audio_handler.cc b/chromeos/ash/components/audio/cras_audio_handler.cc
index e0e8c0a..0284fbf 100644
--- a/chromeos/ash/components/audio/cras_audio_handler.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler.cc
@@ -1234,6 +1234,11 @@
   if (features::IsAudioSelectionImprovementEnabled()) {
     SyncDevicePrefSetMap(active_device.is_input);
     audio_pref_handler_->UpdateMostRecentActivatedDeviceIdList(active_device);
+
+    // Remove notification if the hotplugged device that triggered the
+    // notification has already been activated.
+    audio_selection_notification_handler_.RemoveNotificationIfNecessary(
+        {active_device});
   }
 
   // Save active state for the nodes.
diff --git a/chromeos/ash/components/audio/cras_audio_handler_unittest.cc b/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
index 51143fc..0597786 100644
--- a/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
@@ -7655,4 +7655,51 @@
   EXPECT_EQ(device2->device_name, input_device_name);
 }
 
+// Tests that notification is removed if the hot plugged device that triggered
+// the notification has already been activated via settings or quick settings.
+TEST_P(
+    CrasAudioHandlerTest,
+    RemoveNotificationIfHotPluggedDeviceHasBeenActivated_AudioSelectionImprovementFlagOn) {
+  scoped_feature_list_.InitAndEnableFeature(
+      ash::features::kAudioSelectionImprovement);
+
+  // Initialize with internal speaker.
+  SetupAudioNodesAndExpectActiveNodes(
+      /*initial_nodes=*/{kInternalSpeaker},
+      /*expected_active_input_node=*/nullptr,
+      /*expected_active_output_node=*/kInternalSpeaker,
+      /*expected_has_alternative_input=*/std::nullopt,
+      /*expected_has_alternative_output=*/false);
+
+  // Expect that notification is not displayed since there is only one output
+  // device connected.
+  EXPECT_EQ(0u, GetNotificationCount());
+
+  // Plug in a usb headphone.
+  AudioNodeList audio_nodes;
+  AudioNode internal_speaker = GenerateAudioNode(kInternalSpeaker);
+  internal_speaker.active = true;
+  audio_nodes.push_back(internal_speaker);
+  AudioNode usb_headphone_1 = GenerateAudioNode(kUSBHeadphone1);
+  audio_nodes.push_back(usb_headphone_1);
+  ChangeAudioNodes(audio_nodes);
+
+  // Verify the active output device is not switched because the new connected
+  // device is not seen before.
+  EXPECT_EQ(0, test_observer_->active_output_node_changed_count());
+  ExpectActiveDevice(/*is_input=*/false,
+                     /*expected_active_device=*/kInternalSpeaker,
+                     /*has_alternative_device=*/true);
+
+  // Verify notification is displayed.
+  FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
+  EXPECT_EQ(1u, GetNotificationCount());
+
+  // Manually activate the kUSBHeadphone1, expect that notification is removed.
+  AudioDevice usb_headphone_device(usb_headphone_1);
+  cras_audio_handler_->SwitchToDevice(usb_headphone_device, true,
+                                      DeviceActivateType::kActivateByUser);
+  EXPECT_EQ(0u, GetNotificationCount());
+}
+
 }  // namespace ash