blob: c3bcc6cc69e70545b879ad92f2e3393af3ff389a [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/callback_list.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chrome/browser/ui/global_media_controls/media_item_ui_device_selector_delegate.h"
#include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
#include "chrome/browser/ui/global_media_controls/media_notification_service.h"
#include "chrome/browser/ui/media_router/cast_dialog_model.h"
#include "chrome/browser/ui/media_router/media_route_starter.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/media_router/browser/presentation/start_presentation_context.h"
#include "media/audio/audio_device_description.h"
#include "media/base/media_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/test/button_test_api.h"
using media_router::CastDialogController;
using media_router::CastDialogModel;
using media_router::UIMediaSink;
using media_router::UIMediaSinkState;
using testing::_;
using testing::NiceMock;
namespace {
constexpr char kItemId[] = "item_id";
constexpr char kSinkId[] = "sink_id";
constexpr char kSinkFriendlyName[] = "Nest Hub";
constexpr char16_t kSinkFriendlyName16[] = u"Nest Hub";
ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED,
gfx::Point(),
gfx::Point(),
ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
UIMediaSink CreateMediaSink(
UIMediaSinkState state = UIMediaSinkState::AVAILABLE) {
UIMediaSink sink{media_router::mojom::MediaRouteProviderId::CAST};
sink.friendly_name = kSinkFriendlyName16;
sink.id = kSinkId;
sink.state = state;
sink.cast_modes = {media_router::MediaCastMode::PRESENTATION};
return sink;
}
CastDialogModel CreateModelWithSinks(std::vector<UIMediaSink> sinks) {
CastDialogModel model;
model.set_media_sinks(std::move(sinks));
return model;
}
class MockMediaNotificationDeviceProvider
: public MediaNotificationDeviceProvider {
public:
MockMediaNotificationDeviceProvider() = default;
~MockMediaNotificationDeviceProvider() override = default;
void AddDevice(const std::string& device_name, const std::string& device_id) {
device_descriptions_.emplace_back(device_name, device_id, "");
}
void ResetDevices() { device_descriptions_.clear(); }
void RunUICallback() { output_devices_callback_.Run(device_descriptions_); }
base::CallbackListSubscription RegisterOutputDeviceDescriptionsCallback(
GetOutputDevicesCallback cb) override {
output_devices_callback_ = std::move(cb);
RunUICallback();
return base::CallbackListSubscription();
}
MOCK_METHOD(void,
GetOutputDeviceDescriptions,
(media::AudioSystem::OnDeviceDescriptionsCallback),
(override));
private:
media::AudioDeviceDescriptions device_descriptions_;
GetOutputDevicesCallback output_devices_callback_;
};
class MockMediaItemUIDeviceSelectorDelegate
: public MediaItemUIDeviceSelectorDelegate {
public:
MockMediaItemUIDeviceSelectorDelegate() {
provider_ = std::make_unique<MockMediaNotificationDeviceProvider>();
}
MOCK_METHOD(void,
OnAudioSinkChosen,
(const std::string& item_id, const std::string& sink_id),
(override));
MOCK_METHOD(bool,
OnMediaRemotingRequested,
(const std::string& item_id),
(override));
base::CallbackListSubscription RegisterAudioOutputDeviceDescriptionsCallback(
MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::
CallbackType callback) override {
return provider_->RegisterOutputDeviceDescriptionsCallback(
std::move(callback));
}
MockMediaNotificationDeviceProvider* GetProvider() { return provider_.get(); }
base::CallbackListSubscription
RegisterIsAudioOutputDeviceSwitchingSupportedCallback(
const std::string& item_id,
base::RepeatingCallback<void(bool)> callback) override {
callback.Run(supports_switching);
supports_switching_callback_ = std::move(callback);
return base::CallbackListSubscription();
}
void RunSupportsDeviceSwitchingCallback() {
supports_switching_callback_.Run(supports_switching);
}
bool supports_switching = true;
private:
std::unique_ptr<MockMediaNotificationDeviceProvider> provider_;
base::RepeatingCallback<void(bool)> supports_switching_callback_;
};
class MockCastDialogController : public CastDialogController {
public:
MOCK_METHOD1(AddObserver, void(CastDialogController::Observer* observer));
MOCK_METHOD1(RemoveObserver, void(CastDialogController::Observer* observer));
MOCK_METHOD2(StartCasting,
void(const std::string& sink_id,
media_router::MediaCastMode cast_mode));
MOCK_METHOD1(StopCasting, void(const std::string& route_id));
MOCK_METHOD1(ClearIssue, void(const media_router::Issue::Id& issue_id));
MOCK_METHOD0(TakeMediaRouteStarter,
std::unique_ptr<media_router::MediaRouteStarter>());
};
} // anonymous namespace
class MediaItemUIDeviceSelectorViewTest : public ChromeViewsTestBase {
public:
MediaItemUIDeviceSelectorViewTest() = default;
~MediaItemUIDeviceSelectorViewTest() override = default;
// ChromeViewsTestBase
void SetUp() override {
ChromeViewsTestBase::SetUp();
feature_list_.InitAndEnableFeature(
media::kGlobalMediaControlsSeamlessTransfer);
}
void TearDown() override {
view_.reset();
ChromeViewsTestBase::TearDown();
}
void AddAudioDevices(MockMediaItemUIDeviceSelectorDelegate& delegate) {
auto* provider = delegate.GetProvider();
provider->AddDevice("Speaker", "1");
provider->AddDevice("Headphones", "2");
provider->AddDevice("Earbuds", "3");
}
void SimulateButtonClick(views::View* view) {
views::test::ButtonTestApi(static_cast<views::Button*>(view))
.NotifyClick(pressed_event);
}
void SimulateMouseClick(views::View* view) {
view->OnMousePressed(pressed_event);
}
std::string EntryLabelText(views::View* entry_view) {
return view_->GetEntryLabelForTesting(entry_view);
}
bool IsHighlighted(views::View* entry_view) {
return view_->GetEntryIsHighlightedForTesting(entry_view);
}
std::string GetButtonText(views::View* view) {
return base::UTF16ToUTF8(static_cast<views::LabelButton*>(view)->GetText());
}
views::View* GetDeviceEntryViewsContainer() {
return view_->device_entry_views_container_;
}
std::unique_ptr<MediaItemUIDeviceSelectorView> CreateDeviceSelectorView(
MockMediaItemUIDeviceSelectorDelegate* delegate,
std::unique_ptr<MockCastDialogController> controller =
std::make_unique<NiceMock<MockCastDialogController>>(),
const std::string& current_device = "1",
bool has_audio_output = true,
global_media_controls::GlobalMediaControlsEntryPoint entry_point =
global_media_controls::GlobalMediaControlsEntryPoint::kToolbarIcon) {
auto device_selector_view = std::make_unique<MediaItemUIDeviceSelectorView>(
kItemId, delegate, std::move(controller), has_audio_output,
entry_point);
device_selector_view->UpdateCurrentAudioDevice(current_device);
return device_selector_view;
}
void CallOnModelUpdated(const std::string& sink_id,
media_router::MediaCastMode cast_mode) {
auto cast_connected_sink = CreateMediaSink(UIMediaSinkState::CONNECTED);
view_->OnModelUpdated(CreateModelWithSinks({cast_connected_sink}));
}
std::unique_ptr<MediaItemUIDeviceSelectorView> view_;
base::HistogramTester histogram_tester_;
base::test::ScopedFeatureList feature_list_;
};
TEST_F(MediaItemUIDeviceSelectorViewTest, DeviceButtonsCreated) {
// Buttons should be created for every device reported by the provider.
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(&delegate);
view_->OnModelUpdated(CreateModelWithSinks({CreateMediaSink()}));
ASSERT_TRUE(GetDeviceEntryViewsContainer() != nullptr);
auto container_children = GetDeviceEntryViewsContainer()->children();
ASSERT_EQ(container_children.size(), 4u);
EXPECT_EQ(EntryLabelText(container_children.at(0)), "Speaker");
EXPECT_EQ(EntryLabelText(container_children.at(1)), "Headphones");
EXPECT_EQ(EntryLabelText(container_children.at(2)), "Earbuds");
EXPECT_EQ(EntryLabelText(container_children.at(3)), kSinkFriendlyName);
}
TEST_F(MediaItemUIDeviceSelectorViewTest, ExpandButtonAndLabelCreated) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(&delegate);
EXPECT_EQ(view_->GetExpandDeviceSelectorLabelForTesting()->GetText(),
l10n_util::GetStringUTF16(IDS_GLOBAL_MEDIA_CONTROLS_DEVICES_LABEL));
EXPECT_TRUE(view_->GetDropdownButtonForTesting());
view_ = CreateDeviceSelectorView(
&delegate, std::make_unique<NiceMock<MockCastDialogController>>(), "1",
true,
global_media_controls::GlobalMediaControlsEntryPoint::kPresentation);
EXPECT_EQ(view_->GetExpandDeviceSelectorLabelForTesting()->GetText(),
l10n_util::GetStringUTF16(
IDS_GLOBAL_MEDIA_CONTROLS_DEVICES_LABEL_WITH_COLON));
EXPECT_FALSE(view_->GetDropdownButtonForTesting());
}
TEST_F(MediaItemUIDeviceSelectorViewTest, ExpandButtonOpensEntryContainer) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(&delegate);
// Clicking on the dropdown button should expand the device list.
ASSERT_TRUE(view_->GetDropdownButtonForTesting());
EXPECT_FALSE(view_->GetDeviceEntryViewVisibilityForTesting());
SimulateButtonClick(view_->GetDropdownButtonForTesting());
EXPECT_TRUE(view_->GetDeviceEntryViewVisibilityForTesting());
}
TEST_F(MediaItemUIDeviceSelectorViewTest, ExpandLabelOpensEntryContainer) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(&delegate);
// Clicking on the device selector view should expand the device list.
ASSERT_TRUE(view_.get());
EXPECT_FALSE(view_->GetDeviceEntryViewVisibilityForTesting());
SimulateMouseClick(view_.get());
EXPECT_TRUE(view_->GetDeviceEntryViewVisibilityForTesting());
}
TEST_F(MediaItemUIDeviceSelectorViewTest, DeviceEntryContainerVisibility) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
// The device entry container should be collapsed if the media dialog is
// opened from the toolbar or Chrome OS system tray.
view_ = CreateDeviceSelectorView(&delegate);
EXPECT_FALSE(view_->GetDeviceEntryViewVisibilityForTesting());
// The device entry container should be expanded if the media dialog is opened
// for a presentation request.
view_ = CreateDeviceSelectorView(
&delegate, std::make_unique<NiceMock<MockCastDialogController>>(), "1",
/* has_audio_output */ true,
global_media_controls::GlobalMediaControlsEntryPoint::kPresentation);
EXPECT_TRUE(view_->GetDeviceEntryViewVisibilityForTesting());
}
TEST_F(MediaItemUIDeviceSelectorViewTest,
AudioDeviceButtonClickNotifiesContainer) {
// When buttons are clicked the media notification delegate should be
// informed.
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(&delegate);
EXPECT_CALL(delegate, OnAudioSinkChosen(kItemId, "1")).Times(1);
EXPECT_CALL(delegate, OnAudioSinkChosen(kItemId, "2")).Times(1);
EXPECT_CALL(delegate, OnAudioSinkChosen(kItemId, "3")).Times(1);
for (views::View* child : GetDeviceEntryViewsContainer()->children()) {
SimulateButtonClick(child);
}
}
TEST_F(MediaItemUIDeviceSelectorViewTest,
CastDeviceButtonClickStartsCasting_Presentation) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
auto cast_controller = std::make_unique<NiceMock<MockCastDialogController>>();
auto* cast_controller_ptr = cast_controller.get();
view_ = CreateDeviceSelectorView(&delegate, std::move(cast_controller));
// Clicking on connecting or disconnecting sinks will not start casting.
view_->OnModelUpdated(
CreateModelWithSinks({CreateMediaSink(UIMediaSinkState::CONNECTING),
CreateMediaSink(UIMediaSinkState::DISCONNECTING)}));
EXPECT_CALL(*cast_controller_ptr, StartCasting(_, _)).Times(0);
for (views::View* child : GetDeviceEntryViewsContainer()->children()) {
SimulateButtonClick(child);
}
// Clicking on available or connected CAST sinks will start casting.
auto cast_connected_sink = CreateMediaSink(UIMediaSinkState::CONNECTED);
cast_connected_sink.provider =
media_router::mojom::MediaRouteProviderId::CAST;
auto cast_remote_playback_sink = CreateMediaSink();
cast_remote_playback_sink.cast_modes = {
media_router::MediaCastMode::REMOTE_PLAYBACK};
view_->OnModelUpdated(CreateModelWithSinks(
{CreateMediaSink(), cast_connected_sink, cast_remote_playback_sink}));
EXPECT_CALL(*cast_controller_ptr,
StartCasting(_, media_router::MediaCastMode::PRESENTATION))
.Times(2);
EXPECT_CALL(*cast_controller_ptr,
StartCasting(_, media_router::MediaCastMode::REMOTE_PLAYBACK));
for (views::View* child : GetDeviceEntryViewsContainer()->children()) {
SimulateButtonClick(child);
}
// Clicking on connected DIAL sinks will terminate casting.
// TODO(crbug.com/1206830): change test cases after DIAL MRP supports
// launching session on a connected sink.
auto dial_connected_sink = CreateMediaSink(UIMediaSinkState::CONNECTED);
dial_connected_sink.provider =
media_router::mojom::MediaRouteProviderId::DIAL;
dial_connected_sink.route =
media_router::MediaRoute("routeId1", media_router::MediaSource("source1"),
"sinkId1", "description", true);
view_->OnModelUpdated(CreateModelWithSinks({dial_connected_sink}));
EXPECT_CALL(*cast_controller_ptr, StopCasting("routeId1"));
for (views::View* child : GetDeviceEntryViewsContainer()->children()) {
SimulateButtonClick(child);
}
}
TEST_F(MediaItemUIDeviceSelectorViewTest,
StartCastingTriggersAnotherSinkUpdate) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
auto cast_controller = std::make_unique<NiceMock<MockCastDialogController>>();
auto* cast_controller_ptr = cast_controller.get();
view_ = CreateDeviceSelectorView(&delegate, std::move(cast_controller));
view_->OnModelUpdated(CreateModelWithSinks({CreateMediaSink()}));
EXPECT_CALL(*cast_controller_ptr,
StartCasting(_, media_router::MediaCastMode::PRESENTATION));
// CastDialogController::StartCasting() should create a new route, which
// triggers the MediaRouterUI to broadcast a sink update. As a result
// MediaItemUIDeviceSelectorView::OnModelUpdated() should be called before
// StartCasting() returns. This test verifies that the second the second call
// to OnModelUpdated() does not cause UaF error in
// RecordStartCastingMetrics().
ON_CALL(*cast_controller_ptr, StartCasting(_, _))
.WillByDefault(
Invoke(this, &MediaItemUIDeviceSelectorViewTest::CallOnModelUpdated));
for (views::View* child : GetDeviceEntryViewsContainer()->children()) {
SimulateButtonClick(child);
}
}
TEST_F(MediaItemUIDeviceSelectorViewTest, CurrentAudioDeviceHighlighted) {
// The 'current' audio device should be highlighted in the UI and appear
// before other devices.
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(
&delegate, std::make_unique<NiceMock<MockCastDialogController>>(), "3");
auto* first_entry = GetDeviceEntryViewsContainer()->children().front();
EXPECT_EQ(EntryLabelText(first_entry), "Earbuds");
EXPECT_TRUE(IsHighlighted(first_entry));
}
TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDeviceHighlightedOnChange) {
// When the audio output device changes, the UI should highlight that one.
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(&delegate);
auto& container_children = GetDeviceEntryViewsContainer()->children();
// There should be only one highlighted button. It should be the first button.
// It's text should be "Speaker"
auto highlight_pred = [this](views::View* v) { return IsHighlighted(v); };
EXPECT_EQ(base::ranges::count_if(container_children, highlight_pred), 1);
EXPECT_EQ(base::ranges::find_if(container_children, highlight_pred),
container_children.begin());
EXPECT_EQ(EntryLabelText(container_children.front()), "Speaker");
// Simulate a device change
view_->UpdateCurrentAudioDevice("3");
// The button for "Earbuds" should come before all others & be highlighted.
EXPECT_EQ(base::ranges::count_if(container_children, highlight_pred), 1);
EXPECT_EQ(base::ranges::find_if(container_children, highlight_pred),
container_children.begin());
EXPECT_EQ(EntryLabelText(container_children.front()), "Earbuds");
}
TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDeviceButtonsChange) {
// If the device provider reports a change in connect audio devices, the UI
// should update accordingly.
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
view_ = CreateDeviceSelectorView(&delegate);
auto* provider = delegate.GetProvider();
provider->ResetDevices();
// Make "Monitor" the default device.
provider->AddDevice("Monitor",
media::AudioDeviceDescription::kDefaultDeviceId);
provider->RunUICallback();
{
auto& container_children = GetDeviceEntryViewsContainer()->children();
EXPECT_EQ(container_children.size(), 1u);
ASSERT_FALSE(container_children.empty());
EXPECT_EQ(EntryLabelText(container_children.front()), "Monitor");
// When the device highlighted in the UI is removed, the UI should fall back
// to highlighting the default device.
EXPECT_TRUE(IsHighlighted(container_children.front()));
}
provider->ResetDevices();
AddAudioDevices(delegate);
provider->RunUICallback();
{
auto& container_children = GetDeviceEntryViewsContainer()->children();
EXPECT_EQ(container_children.size(), 3u);
ASSERT_FALSE(container_children.empty());
// When the device highlighted in the UI is removed, and there is no default
// device, the UI should not highlight any of the devices.
for (auto* device_view : container_children) {
EXPECT_FALSE(IsHighlighted(device_view));
}
}
}
TEST_F(MediaItemUIDeviceSelectorViewTest, VisibilityChanges) {
// The device selector view should become hidden when there is only one
// unique device, unless there exists a cast device.
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
auto* provider = delegate.GetProvider();
provider->AddDevice("Speaker", "1");
provider->AddDevice(media::AudioDeviceDescription::GetDefaultDeviceName(),
media::AudioDeviceDescription::kDefaultDeviceId);
view_ = CreateDeviceSelectorView(
&delegate, std::make_unique<NiceMock<MockCastDialogController>>(),
media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_FALSE(view_->GetVisible());
testing::Mock::VerifyAndClearExpectations(&delegate);
view_->OnModelUpdated(CreateModelWithSinks({CreateMediaSink()}));
EXPECT_TRUE(view_->GetVisible());
testing::Mock::VerifyAndClearExpectations(&delegate);
view_->OnModelUpdated(CreateModelWithSinks({}));
provider->ResetDevices();
provider->AddDevice("Speaker", "1");
provider->AddDevice("Headphones",
media::AudioDeviceDescription::kDefaultDeviceId);
provider->RunUICallback();
EXPECT_TRUE(view_->GetVisible());
testing::Mock::VerifyAndClearExpectations(&delegate);
provider->ResetDevices();
provider->AddDevice("Speaker", "1");
provider->AddDevice("Headphones", "2");
provider->AddDevice(media::AudioDeviceDescription::GetDefaultDeviceName(),
media::AudioDeviceDescription::kDefaultDeviceId);
provider->RunUICallback();
EXPECT_TRUE(view_->GetVisible());
testing::Mock::VerifyAndClearExpectations(&delegate);
}
TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDeviceChangeIsNotSupported) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
delegate.supports_switching = false;
view_ = CreateDeviceSelectorView(
&delegate, std::make_unique<NiceMock<MockCastDialogController>>(),
media::AudioDeviceDescription::kDefaultDeviceId);
EXPECT_FALSE(view_->GetVisible());
delegate.supports_switching = true;
delegate.RunSupportsDeviceSwitchingCallback();
EXPECT_TRUE(view_->GetVisible());
}
TEST_F(MediaItemUIDeviceSelectorViewTest, CastDeviceButtonClickClearsIssue) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
auto cast_controller = std::make_unique<NiceMock<MockCastDialogController>>();
auto* cast_controller_ptr = cast_controller.get();
view_ = CreateDeviceSelectorView(&delegate, std::move(cast_controller));
// Clicking on sinks with issue will clear up the issue instead of starting a
// cast session.
auto sink = CreateMediaSink();
media_router::IssueInfo issue_info(
"Issue Title", media_router::IssueInfo::Severity::WARNING);
media_router::Issue issue(issue_info);
sink.issue = issue;
view_->OnModelUpdated(CreateModelWithSinks({sink}));
EXPECT_CALL(*cast_controller_ptr, StartCasting(_, _)).Times(0);
EXPECT_CALL(*cast_controller_ptr, ClearIssue(issue.id()));
for (views::View* child : GetDeviceEntryViewsContainer()->children()) {
SimulateButtonClick(child);
}
}
TEST_F(MediaItemUIDeviceSelectorViewTest, AudioDevicesCountHistogramRecorded) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
AddAudioDevices(delegate);
histogram_tester_.ExpectTotalCount(kAudioDevicesCountHistogramName, 0);
view_ =
CreateDeviceSelectorView(&delegate, /* CastDialogController */ nullptr);
view_->ShowDevices();
histogram_tester_.ExpectTotalCount(kAudioDevicesCountHistogramName, 1);
histogram_tester_.ExpectBucketCount(kAudioDevicesCountHistogramName, 3, 1);
auto* provider = delegate.GetProvider();
provider->AddDevice("Monitor", "4");
provider->RunUICallback();
histogram_tester_.ExpectTotalCount(kAudioDevicesCountHistogramName, 1);
histogram_tester_.ExpectBucketCount(kAudioDevicesCountHistogramName, 3, 1);
}
TEST_F(MediaItemUIDeviceSelectorViewTest,
DeviceSelectorAvailableHistogramRecorded) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
auto* provider = delegate.GetProvider();
provider->AddDevice("Speaker",
media::AudioDeviceDescription::kDefaultDeviceId);
delegate.supports_switching = false;
histogram_tester_.ExpectTotalCount(kDeviceSelectorAvailableHistogramName, 0);
view_ =
CreateDeviceSelectorView(&delegate, /* CastDialogController */ nullptr);
EXPECT_FALSE(view_->GetVisible());
histogram_tester_.ExpectTotalCount(kDeviceSelectorAvailableHistogramName, 0);
provider->AddDevice("Headphones", "2");
provider->RunUICallback();
EXPECT_FALSE(view_->GetVisible());
histogram_tester_.ExpectTotalCount(kDeviceSelectorAvailableHistogramName, 0);
view_.reset();
histogram_tester_.ExpectTotalCount(kDeviceSelectorAvailableHistogramName, 1);
histogram_tester_.ExpectBucketCount(kDeviceSelectorAvailableHistogramName,
false, 1);
delegate.supports_switching = true;
view_ =
CreateDeviceSelectorView(&delegate, /* CastDialogController */ nullptr);
EXPECT_TRUE(view_->GetVisible());
histogram_tester_.ExpectTotalCount(kDeviceSelectorAvailableHistogramName, 2);
histogram_tester_.ExpectBucketCount(kDeviceSelectorAvailableHistogramName,
true, 1);
view_.reset();
histogram_tester_.ExpectTotalCount(kDeviceSelectorAvailableHistogramName, 2);
}
TEST_F(MediaItemUIDeviceSelectorViewTest,
DeviceSelectorOpenedHistogramRecorded) {
NiceMock<MockMediaItemUIDeviceSelectorDelegate> delegate;
auto* provider = delegate.GetProvider();
provider->AddDevice("Speaker",
media::AudioDeviceDescription::kDefaultDeviceId);
provider->AddDevice("Headphones", "2");
delegate.supports_switching = false;
histogram_tester_.ExpectTotalCount(kDeviceSelectorOpenedHistogramName, 0);
view_ =
CreateDeviceSelectorView(&delegate, /* CastDialogController */ nullptr);
EXPECT_FALSE(view_->GetVisible());
view_.reset();
// The histrogram should not be recorded when the device selector is not
// available.
histogram_tester_.ExpectTotalCount(kDeviceSelectorOpenedHistogramName, 0);
delegate.supports_switching = true;
view_ =
CreateDeviceSelectorView(&delegate, /* CastDialogController */ nullptr);
view_.reset();
histogram_tester_.ExpectTotalCount(kDeviceSelectorOpenedHistogramName, 1);
histogram_tester_.ExpectBucketCount(kDeviceSelectorOpenedHistogramName, false,
1);
view_ =
CreateDeviceSelectorView(&delegate, /* CastDialogController */ nullptr);
view_->ShowDevices();
histogram_tester_.ExpectTotalCount(kDeviceSelectorOpenedHistogramName, 2);
histogram_tester_.ExpectBucketCount(kDeviceSelectorOpenedHistogramName, true,
1);
view_.reset();
histogram_tester_.ExpectTotalCount(kDeviceSelectorOpenedHistogramName, 2);
}