// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/audio/audio_selection_notification_handler.h"
#include <optional>
#include "ash/strings/grit/ash_strings.h"
#include "base/test/metrics/histogram_tester.h"
#include "chromeos/ash/components/audio/audio_device.h"
#include "chromeos/ash/components/audio/audio_device_selection_test_base.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
class AudioSelectionNotificationHandlerTest
: public AudioDeviceSelectionTestBase {
public:
void SetUp() override { message_center::MessageCenter::Initialize(); }
void TearDown() override { message_center::MessageCenter::Shutdown(); }
const base::HistogramTester& histogram_tester() { return histogram_tester_; }
static void SwitchToDevice(const AudioDevice& device,
bool notify,
DeviceActivateType activate_by) {
CrasAudioHandler::Get()->SwitchToDevice(device, notify, activate_by);
}
static void OpenSettingsAudioPage() {
CrasAudioHandler::Get()->OpenSettingsAudioPage();
}
AudioSelectionNotificationHandler& audio_selection_notification_handler() {
return audio_selection_notification_handler_;
}
bool AudioNodesBelongToSameSource(const AudioDevice& input_device,
const AudioDevice& output_device) {
return audio_selection_notification_handler_.AudioNodesBelongToSameSource(
input_device, output_device);
}
void FakeSwitchToDevice(const AudioDevice& device,
bool notify,
DeviceActivateType activate_by) {
if (device.is_input) {
active_input_id_ = device.id;
} else {
active_output_id_ = device.id;
}
}
void HandleSwitchButtonClicked(
const AudioDeviceList& devices_to_activate,
AudioSelectionNotificationHandler::NotificationType notification_type,
std::optional<int> button_index) {
audio_selection_notification_handler_.HandleSwitchButtonClicked(
devices_to_activate,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::FakeSwitchToDevice,
weak_ptr_factory_.GetWeakPtr()),
notification_type, button_index);
}
void FakeOpenSettingsPage() { settings_audio_page_opened_ = true; }
void HandleSettingsButtonClicked(std::optional<int> button_index) {
audio_selection_notification_handler_.HandleSettingsButtonClicked(
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::FakeOpenSettingsPage,
weak_ptr_factory_.GetWeakPtr()),
button_index);
}
// Gets the count of audio selection notification.
size_t GetNotificationCount() {
auto* message_center = message_center::MessageCenter::Get();
return message_center->NotificationCount();
}
// Gets the title of audio selection notification. If not found, return
// std::nullopt.
const std::optional<std::u16string> GetNotificationTitle() {
auto* message_center = message_center::MessageCenter::Get();
message_center::Notification* notification =
message_center->FindNotificationById(
AudioSelectionNotificationHandler::kAudioSelectionNotificationId);
return notification ? std::make_optional(notification->title())
: std::nullopt;
}
// Gets the message of audio selection notification. If not found, return
// std::nullopt.
const std::optional<std::u16string> GetNotificationMessage() {
auto* message_center = message_center::MessageCenter::Get();
message_center::Notification* notification =
message_center->FindNotificationById(
AudioSelectionNotificationHandler::kAudioSelectionNotificationId);
return notification ? std::make_optional(notification->message())
: std::nullopt;
}
uint64_t active_input_id() { return active_input_id_; }
uint64_t active_output_id() { return active_output_id_; }
bool settings_audio_page_opened() { return settings_audio_page_opened_; }
private:
AudioSelectionNotificationHandler audio_selection_notification_handler_;
// Initialized with an invalid id, to fix an issue in build: MemorySanitizer:
// use-of-uninitialized-value.
uint64_t active_input_id_ = 0;
uint64_t active_output_id_ = 0;
base::HistogramTester histogram_tester_;
bool settings_audio_page_opened_ = false;
base::WeakPtrFactory<AudioSelectionNotificationHandlerTest> weak_ptr_factory_{
this};
};
TEST_F(AudioSelectionNotificationHandlerTest, ShowAudioSelectionNotification) {
EXPECT_EQ(0u, GetNotificationCount());
AudioDeviceList hotplug_input_devices = {
AudioDevice(NewInputNode("INTERNAL_MIC"))};
AudioDeviceList hotplug_output_devices = {
AudioDevice(NewOutputNode("INTERNAL_SPEAKER"))};
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
// Expect notification is shown.
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
// Expect new notification to replace the old one and the current notification
// count does not change.
hotplug_input_devices = {AudioDevice(NewInputNode("MIC"))};
hotplug_output_devices = {AudioDevice(NewOutputNode("HEADPHONE"))};
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
EXPECT_EQ(1u, GetNotificationCount());
}
// Tests that AudioNodesBelongToSameSource can tell if one audio input device
// and one audio output device belong to the same physical audio device.
TEST_F(AudioSelectionNotificationHandlerTest, AudioNodesBelongToSameSource) {
struct {
const AudioDevice input_device;
const AudioDevice output_device;
bool same_source;
} items[] = {
{AudioDevice(NewInputNode("INTERNAL_MIC")),
AudioDevice(NewOutputNode("INTERNAL_SPEAKER")), true},
{AudioDevice(NewInputNode("FRONT_MIC")),
AudioDevice(NewOutputNode("INTERNAL_SPEAKER")), true},
{AudioDevice(NewInputNode("REAR_MIC")),
AudioDevice(NewOutputNode("INTERNAL_SPEAKER")), true},
{AudioDevice(NewInputNode("MIC")),
AudioDevice(NewOutputNode("HEADPHONE")), true},
{AudioDevice(NewInputNode("BLUETOOTH_NB_MIC")),
AudioDevice(NewOutputNode("BLUETOOTH")), true},
{AudioDevice(NewNodeWithName(/*is_input=*/true, "USB",
"Razer USB Sound Card: USB Audio:2,0: Mic")),
AudioDevice(
NewNodeWithName(/*is_input=*/false, "USB",
"Razer USB Sound Card: USB Audio:2,0: Speaker")),
true},
{AudioDevice(NewNodeWithName(/*is_input=*/true, "BLUETOOTH", "Airpods")),
AudioDevice(NewNodeWithName(/*is_input=*/false, "BLUETOOTH", "Airpods")),
true},
// Audio devices with different types do not belong to the same physical
// device.
{AudioDevice(NewInputNode("INTERNAL_MIC")),
AudioDevice(NewOutputNode("HEADPHONE")), false},
// Audio devices with different types do not belong to the same physical
// device.
{AudioDevice(NewInputNode("BLUETOOTH")),
AudioDevice(NewOutputNode("HDMI")), false},
// Audio devices with different types do not belong to the same physical
// device.
{AudioDevice(NewInputNode("USB")), AudioDevice(NewOutputNode("HDMI")),
false},
// Audio devices with different types do not belong to the same physical
// device.
{AudioDevice(NewInputNode("BLUETOOTH")),
AudioDevice(NewOutputNode("USB")), false},
// Audio devices with different device source names do not belong to the
// same physical device.
{AudioDevice(
NewNodeWithName(/*is_input=*/true, "BLUETOOTH", "Airpods Pro")),
AudioDevice(NewNodeWithName(/*is_input=*/false, "BLUETOOTH", "Airpods")),
false},
// Audio devices with different device source names do not belong to the
// same physical device.
{AudioDevice(NewNodeWithName(/*is_input=*/true, "USB",
"Razer USB Sound Card: USB Audio:2,0: Mic")),
AudioDevice(NewNodeWithName(/*is_input=*/false, "USB",
"CS201 USB AUDIO: USB Audio:2,0: PCM")),
false},
};
for (const auto& item : items) {
EXPECT_EQ(item.same_source, AudioNodesBelongToSameSource(
item.input_device, item.output_device));
}
}
// Tests audio selection notification with input only displays correctly.
TEST_F(AudioSelectionNotificationHandlerTest,
Notification_SingleSourceWithInputOnly) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug a web cam input.
const std::string input_device_name =
"HD Pro Webcam C920: USB Audio:2,0: Mic";
AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
/*is_input=*/true, "USB", input_device_name))};
AudioDeviceList hotplug_output_devices = {};
// No notification event metrics are fired before showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
std::optional<std::u16string> title = GetNotificationTitle();
EXPECT_TRUE(title.has_value());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_TITLE),
title.value());
std::optional<std::u16string> message = GetNotificationMessage();
EXPECT_TRUE(message.has_value());
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
base::UTF8ToUTF16(input_device_name)),
message.value());
// Notification event metrics are fired after showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithInputOnlyDeviceShowsUp,
1);
// Clicking switch button, expect clicking notification event is fired.
HandleSwitchButtonClicked(hotplug_input_devices,
AudioSelectionNotificationHandler::
NotificationType::kSingleSourceWithInputOnly,
/*button_index=*/1);
// Clicking notification event metrics are fired after clicking switch button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 2);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithInputOnlyDeviceClicked,
1);
}
// Tests audio selection notification with output only displays correctly.
TEST_F(AudioSelectionNotificationHandlerTest,
Notification_SingleSourceWithOutputOnly) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug HDMI display with audio output.
AudioDeviceList hotplug_input_devices = {};
const std::string output_device_name = "Sceptre Z27";
AudioDeviceList hotplug_output_devices = {AudioDevice(NewNodeWithName(
/*is_input=*/false, "HDMI", output_device_name))};
// No notification event metrics are fired before showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
std::optional<std::u16string> title = GetNotificationTitle();
EXPECT_TRUE(title.has_value());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_OUTPUT_TITLE),
title.value());
std::optional<std::u16string> message = GetNotificationMessage();
EXPECT_TRUE(message.has_value());
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_OR_OUTPUT_BODY,
base::UTF8ToUTF16(output_device_name)),
message.value());
// Notification event metrics are fired after showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithOutputOnlyDeviceShowsUp,
1);
// Clicking switch button, expect clicking notification event is fired.
HandleSwitchButtonClicked(hotplug_output_devices,
AudioSelectionNotificationHandler::
NotificationType::kSingleSourceWithOutputOnly,
/*button_index=*/1);
// Clicking notification event metrics are fired after clicking switch button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 2);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithOutputOnlyDeviceClicked,
1);
}
// Tests audio selection notification with single source and both input and
// output displays correctly.
TEST_F(AudioSelectionNotificationHandlerTest,
Notification_SingleSourceWithBothInputAndOutput) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug a USB input and a USB output device from the same source.
const std::string device_source_name = "Razer USB Sound Card";
const std::string input_device_name =
device_source_name + ": USB Audio:2,0: Mic";
const AudioDevice input_device = AudioDevice(NewNodeWithName(
/*is_input=*/true, "USB", input_device_name));
AudioDeviceList hotplug_input_devices = {input_device};
const std::string output_device_name =
device_source_name + ": USB Audio:2,0: Speaker";
const AudioDevice output_device = AudioDevice(
NewNodeWithName(/*is_input=*/false, "USB", output_device_name));
AudioDeviceList hotplug_output_devices = {output_device};
// No notification event metrics are fired before showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
std::optional<std::u16string> title = GetNotificationTitle();
EXPECT_TRUE(title.has_value());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_SWITCH_SOURCE_TITLE),
title.value());
std::optional<std::u16string> message = GetNotificationMessage();
EXPECT_TRUE(message.has_value());
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_ASH_AUDIO_SELECTION_SWITCH_INPUT_AND_OUTPUT_BODY,
base::UTF8ToUTF16(device_source_name)),
message.value());
// Notification event metrics are fired after showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithBothInputAndOutputDevicesShowsUp,
1);
// Clicking switch button, expect clicking notification event is fired.
HandleSwitchButtonClicked(
{input_device, output_device},
AudioSelectionNotificationHandler::NotificationType::
kSingleSourceWithInputAndOutput,
/*button_index=*/1);
// Clicking notification event metrics are fired after clicking switch button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 2);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithBothInputAndOutputDevicesClicked,
1);
}
// Tests audio selection notification with multiple audio sources displays
// correctly.
TEST_F(AudioSelectionNotificationHandlerTest,
Notification_MultipleSources_SameAudioTypes) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug a USB input and a USB output device from different sources.
const std::string input_device_name = "CS201 USB AUDIO: USB Audio:2,0: Mic";
AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
/*is_input=*/true, "USB", input_device_name))};
const std::string output_device_name =
"Razer USB Sound Card: USB Audio:2,0: Speaker";
AudioDeviceList hotplug_output_devices = {AudioDevice(
NewNodeWithName(/*is_input=*/false, "USB", output_device_name))};
const std::string current_active_input = "internal_mic";
const std::string current_active_output = "internal_speaker";
// No notification event metrics are fired before showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, current_active_input,
current_active_output,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
std::optional<std::u16string> title = GetNotificationTitle();
EXPECT_TRUE(title.has_value());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_TITLE),
title.value());
std::optional<std::u16string> message = GetNotificationMessage();
EXPECT_TRUE(message.has_value());
EXPECT_EQ(
l10n_util::GetStringFUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_BODY,
base::UTF8ToUTF16(current_active_input),
base::UTF8ToUTF16(current_active_output)),
message.value());
// Notification event metrics are fired after showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithMultipleSourcesDevicesShowsUp,
1);
}
// Tests audio selection notification with multiple audio sources displays
// correctly, when current active device name not available.
TEST_F(AudioSelectionNotificationHandlerTest,
Notification_MultipleSources_NameUnavailable) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug a USB input and a USB output device from different sources.
const std::string input_device_name = "CS201 USB AUDIO: USB Audio:2,0: Mic";
AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
/*is_input=*/true, "USB", input_device_name))};
const std::string output_device_name =
"Razer USB Sound Card: USB Audio:2,0: Speaker";
AudioDeviceList hotplug_output_devices = {AudioDevice(
NewNodeWithName(/*is_input=*/false, "USB", output_device_name))};
// No notification event metrics are fired before showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices,
/*active_input_device_name=*/std::nullopt,
/*active_output_device_name=*/std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
std::optional<std::u16string> title = GetNotificationTitle();
EXPECT_TRUE(title.has_value());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_TITLE),
title.value());
std::optional<std::u16string> message = GetNotificationMessage();
EXPECT_TRUE(message.has_value());
EXPECT_EQ(
l10n_util::GetStringUTF16(
IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_BODY_WITH_NAME_UNAVAILABLE),
message.value());
// Notification event metrics are fired after showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithMultipleSourcesDevicesShowsUp,
1);
}
// Tests audio selection notification with multiple audio sources displays
// correctly.
TEST_F(AudioSelectionNotificationHandlerTest,
Notification_MultipleSources_DifferentAudioTypes) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug a USB input and a USB output device from different sources.
const std::string input_device_name =
"HD Pro Webcam C920: USB Audio:2,0: Mic";
AudioDeviceList hotplug_input_devices = {AudioDevice(NewNodeWithName(
/*is_input=*/true, "USB", input_device_name))};
const std::string output_device_name = "Sceptre Z27";
AudioDeviceList hotplug_output_devices = {AudioDevice(NewNodeWithName(
/*is_input=*/false, "HDMI", output_device_name))};
const std::string current_active_input = "internal_mic";
const std::string current_active_output = "internal_speaker";
// No notification event metrics are fired before showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, current_active_input,
current_active_output,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
std::optional<std::u16string> title = GetNotificationTitle();
EXPECT_TRUE(title.has_value());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_TITLE),
title.value());
std::optional<std::u16string> message = GetNotificationMessage();
EXPECT_TRUE(message.has_value());
EXPECT_EQ(
l10n_util::GetStringFUTF16(IDS_ASH_AUDIO_SELECTION_MULTIPLE_DEVICES_BODY,
base::UTF8ToUTF16(current_active_input),
base::UTF8ToUTF16(current_active_output)),
message.value());
// Notification event metrics are fired after showing notification.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithMultipleSourcesDevicesShowsUp,
1);
}
// Tests clicking switch button on notification should activate the device.
TEST_F(AudioSelectionNotificationHandlerTest, HandleSwitchButtonClicked) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug HTMI display with audio output.
AudioDeviceList hotplug_input_devices = {};
const std::string output_device_name = "Sceptre Z27";
const AudioDevice output_hdmi = AudioDevice(NewNodeWithName(
/*is_input=*/false, "HDMI", output_device_name));
AudioDeviceList hotplug_output_devices = {output_hdmi};
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
// Expect notification displays.
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
// Clicking notification body does not have any effects.
HandleSwitchButtonClicked({output_hdmi},
AudioSelectionNotificationHandler::
NotificationType::kSingleSourceWithOutputOnly,
std::nullopt);
EXPECT_NE(output_hdmi.id, active_output_id());
EXPECT_EQ(1u, GetNotificationCount());
// Clicking switch button, expect device being activated and notification is
// removed.
HandleSwitchButtonClicked({output_hdmi},
AudioSelectionNotificationHandler::
NotificationType::kSingleSourceWithOutputOnly,
/*button_index=*/1);
EXPECT_EQ(output_hdmi.id, active_output_id());
EXPECT_EQ(0u, GetNotificationCount());
}
// Tests clicking switch button on notification should fire notification event
// metrics.
TEST_F(AudioSelectionNotificationHandlerTest,
HandleSwitchButtonClicked_OutputOnly) {
// No clicking notification event metrics are fired before clicking switch
// button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithOutputOnlyDeviceClicked,
0);
// Clicking switch button, expect device being activated and notification is
// removed.
HandleSwitchButtonClicked({AudioDevice(NewOutputNode("HDMI"))},
AudioSelectionNotificationHandler::
NotificationType::kSingleSourceWithOutputOnly,
/*button_index=*/1);
// Clicking notification event metrics are fired after clicking switch button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithOutputOnlyDeviceClicked,
1);
}
// Tests clicking switch button on notification should fire notification event
// metrics.
TEST_F(AudioSelectionNotificationHandlerTest,
HandleSwitchButtonClicked_InputOnly) {
// No clicking notification event metrics are fired before clicking switch
// button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithInputOnlyDeviceClicked,
0);
// Clicking switch button, expect device being activated and notification is
// removed.
HandleSwitchButtonClicked({AudioDevice(NewInputNode("USB"))},
AudioSelectionNotificationHandler::
NotificationType::kSingleSourceWithInputOnly,
/*button_index=*/1);
// Clicking notification event metrics are fired after clicking switch button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithInputOnlyDeviceClicked,
1);
}
// Tests clicking switch button on notification should fire notification event
// metrics.
TEST_F(AudioSelectionNotificationHandlerTest,
HandleSwitchButtonClicked_InputAndOutput) {
// No clicking notification event metrics are fired before clicking switch
// button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 0);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithBothInputAndOutputDevicesClicked,
0);
// Clicking switch button, expect device being activated and notification is
// removed.
HandleSwitchButtonClicked(
{AudioDevice(NewInputNode("USB")), AudioDevice(NewOutputNode("HDMI"))},
AudioSelectionNotificationHandler::NotificationType::
kSingleSourceWithInputAndOutput,
/*button_index=*/1);
// Clicking notification event metrics are fired after clicking switch button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithBothInputAndOutputDevicesClicked,
1);
}
// Tests audio selection notification was removed because the hotplugged input
// device is disconnected.
TEST_F(AudioSelectionNotificationHandlerTest,
RemoveNotificationIfInputDeviceIsDisConnected) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug a web cam input.
const std::string input_device_name =
"HD Pro Webcam C920: USB Audio:2,0: Mic";
const AudioDevice input_device = AudioDevice(NewNodeWithName(
/*is_input=*/true, "USB", input_device_name));
AudioDeviceList hotplug_input_devices = {input_device};
AudioDeviceList hotplug_output_devices = {};
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
// If a non related device is removed, notification should stay.
const AudioDevice output_device = AudioDevice(NewOutputNode("USB"));
audio_selection_notification_handler()
.RemoveNotificationIfHotpluggedDeviceActivated({output_device});
EXPECT_EQ(1u, GetNotificationCount());
// If the device that triggers the notification is removed, notification
// should be removed too.
audio_selection_notification_handler()
.RemoveNotificationIfHotpluggedDeviceActivated({input_device});
EXPECT_EQ(0u, GetNotificationCount());
}
// Tests audio selection notification was removed because the hotplugged output
// device is disconnected.
TEST_F(AudioSelectionNotificationHandlerTest,
RemoveNotificationIfOutputDeviceIsDisConnected) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug a web cam input.
const std::string output_device_name =
"HD Pro Webcam C920: USB Audio:2,0: Speaker";
const AudioDevice output_device = AudioDevice(NewNodeWithName(
/*is_input=*/false, "USB", output_device_name));
AudioDeviceList hotplug_output_devices = {output_device};
AudioDeviceList hotplug_input_devices = {};
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
// If a non related device is removed, notification should stay.
const AudioDevice input_device = AudioDevice(NewInputNode("USB"));
audio_selection_notification_handler()
.RemoveNotificationIfHotpluggedDeviceActivated({input_device});
EXPECT_EQ(1u, GetNotificationCount());
// If the device that triggers the notification is removed, notification
// should be removed too.
audio_selection_notification_handler()
.RemoveNotificationIfHotpluggedDeviceActivated({output_device});
EXPECT_EQ(0u, GetNotificationCount());
}
// Tests clicking Settings button on notification should open OS Settings audio
// page.
TEST_F(AudioSelectionNotificationHandlerTest, HandleSettingsButtonClicked) {
EXPECT_EQ(0u, GetNotificationCount());
// Plug HTMI display and a USB output device.
AudioDeviceList hotplug_input_devices = {};
const AudioDevice output_hdmi = AudioDevice(NewNodeWithName(
/*is_input=*/false, "HDMI", "Sceptre Z27"));
const AudioDevice output_USB = AudioDevice(NewNodeWithName(
/*is_input=*/false, "USB", "USB output device"));
AudioDeviceList hotplug_output_devices = {output_hdmi, output_USB};
audio_selection_notification_handler().ShowAudioSelectionNotification(
hotplug_input_devices, hotplug_output_devices, std::nullopt, std::nullopt,
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::SwitchToDevice),
base::BindRepeating(
&AudioSelectionNotificationHandlerTest::OpenSettingsAudioPage));
// Expect notification displays.
FastForwardBy(AudioSelectionNotificationHandler::kDebounceTime);
EXPECT_EQ(1u, GetNotificationCount());
// No clicking notification event metrics are fired before clicking settings
// button, only the notification showing up metrics are fired.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithMultipleSourcesDevicesShowsUp,
1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithMultipleSourcesDevicesClicked,
0);
// Clicking notification body does not have any effects.
HandleSettingsButtonClicked(std::nullopt);
EXPECT_EQ(1u, GetNotificationCount());
// Clicking Settings button, expect OS Settings audio page is opened and
// notification is removed.
HandleSettingsButtonClicked(/*button_index=*/1);
EXPECT_TRUE(settings_audio_page_opened());
EXPECT_EQ(0u, GetNotificationCount());
// Clicking notification event metrics are fired after clicking settings
// button.
histogram_tester().ExpectTotalCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification, 2);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithMultipleSourcesDevicesShowsUp,
1);
histogram_tester().ExpectBucketCount(
AudioDeviceMetricsHandler::kAudioSelectionNotification,
AudioDeviceMetricsHandler::AudioSelectionNotificationEvents::
kNotificationWithMultipleSourcesDevicesClicked,
1);
}
} // namespace ash