blob: bb52deec224fdd60745c8d134e22eff64d0dc7a6 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/media_router/media_router_ui_for_test.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/media/router/media_router_factory.h"
#include "chrome/browser/media/router/media_routes_observer.h"
#include "chrome/browser/ui/media_router/media_router_file_dialog.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_sink_button.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_view.h"
#include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
namespace media_router {
namespace {
ui::MouseEvent CreateMousePressedEvent() {
return ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, 0);
}
ui::MouseEvent CreateMouseReleasedEvent() {
return ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, 0);
}
// Routes observer that calls a callback once there are no routes.
class NoRoutesObserver : public MediaRoutesObserver {
public:
NoRoutesObserver(MediaRouter* router, base::OnceClosure callback)
: MediaRoutesObserver(router), callback_(std::move(callback)) {}
~NoRoutesObserver() override = default;
void OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) override {
if (callback_ && routes.empty())
std::move(callback_).Run();
}
private:
base::OnceClosure callback_;
};
// File dialog with a preset file URL.
class TestMediaRouterFileDialog : public MediaRouterFileDialog {
public:
TestMediaRouterFileDialog(MediaRouterFileDialogDelegate* delegate, GURL url)
: MediaRouterFileDialog(nullptr), delegate_(delegate), file_url_(url) {}
~TestMediaRouterFileDialog() override {}
GURL GetLastSelectedFileUrl() override { return file_url_; }
void OpenFileDialog(Browser* browser) override {
delegate_->FileDialogFileSelected(ui::SelectedFileInfo());
}
private:
MediaRouterFileDialogDelegate* delegate_;
GURL file_url_;
};
// File dialog which fails on open.
class TestFailMediaRouterFileDialog : public MediaRouterFileDialog {
public:
TestFailMediaRouterFileDialog(MediaRouterFileDialogDelegate* delegate,
const IssueInfo& issue)
: MediaRouterFileDialog(nullptr), delegate_(delegate), issue_(issue) {}
~TestFailMediaRouterFileDialog() override {}
void OpenFileDialog(Browser* browser) override {
delegate_->FileDialogSelectionFailed(issue_);
}
private:
MediaRouterFileDialogDelegate* delegate_;
const IssueInfo issue_;
};
} // namespace
// static
MediaRouterUiForTest* MediaRouterUiForTest::GetOrCreateForWebContents(
content::WebContents* web_contents) {
// No-op if an instance already exists for the WebContents.
MediaRouterUiForTest::CreateForWebContents(web_contents);
return MediaRouterUiForTest::FromWebContents(web_contents);
}
MediaRouterUiForTest::~MediaRouterUiForTest() {
CHECK(!watch_callback_);
}
void MediaRouterUiForTest::TearDown() {
if (IsDialogShown())
HideDialog();
}
void MediaRouterUiForTest::ShowDialog() {
dialog_controller_->ShowMediaRouterDialog();
base::RunLoop().RunUntilIdle();
}
void MediaRouterUiForTest::HideDialog() {
dialog_controller_->HideMediaRouterDialog();
base::RunLoop().RunUntilIdle();
}
bool MediaRouterUiForTest::IsDialogShown() const {
return dialog_controller_->IsShowingMediaRouterDialog();
}
void MediaRouterUiForTest::ChooseSourceType(
CastDialogView::SourceType source_type) {
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
dialog_view->ButtonPressed(dialog_view->sources_button_for_test(),
CreateMousePressedEvent());
int source_index;
switch (source_type) {
case CastDialogView::kTab:
source_index = 0;
break;
case CastDialogView::kDesktop:
source_index = 1;
break;
case CastDialogView::kLocalFile:
source_index = 2;
break;
}
dialog_view->sources_menu_model_for_test()->ActivatedAt(source_index);
}
CastDialogView::SourceType MediaRouterUiForTest::GetChosenSourceType() const {
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
return dialog_view->selected_source_;
}
void MediaRouterUiForTest::StartCasting(const std::string& sink_name) {
CastDialogSinkButton* sink_button = GetSinkButton(sink_name);
CHECK(sink_button->enabled());
sink_button->OnMousePressed(CreateMousePressedEvent());
sink_button->OnMouseReleased(CreateMouseReleasedEvent());
base::RunLoop().RunUntilIdle();
}
void MediaRouterUiForTest::StopCasting(const std::string& sink_name) {
CastDialogSinkButton* sink_button = GetSinkButton(sink_name);
sink_button->OnMousePressed(CreateMousePressedEvent());
sink_button->OnMouseReleased(CreateMouseReleasedEvent());
base::RunLoop().RunUntilIdle();
}
void MediaRouterUiForTest::StopCasting() {
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
for (CastDialogSinkButton* sink_button :
dialog_view->sink_buttons_for_test()) {
if (sink_button->sink().state == UIMediaSinkState::CONNECTED) {
sink_button->OnMousePressed(CreateMousePressedEvent());
sink_button->OnMouseReleased(CreateMouseReleasedEvent());
base::RunLoop().RunUntilIdle();
return;
}
}
NOTREACHED() << "Sink was not found";
}
void MediaRouterUiForTest::WaitForSink(const std::string& sink_name) {
ObserveDialog(WatchType::kSink, sink_name);
}
void MediaRouterUiForTest::WaitForSinkAvailable(const std::string& sink_name) {
ObserveDialog(WatchType::kSinkAvailable, sink_name);
}
void MediaRouterUiForTest::WaitForAnyIssue() {
ObserveDialog(WatchType::kAnyIssue);
}
void MediaRouterUiForTest::WaitForAnyRoute() {
ObserveDialog(WatchType::kAnyRoute);
}
void MediaRouterUiForTest::WaitForDialogShown() {
CHECK(!watch_sink_name_);
CHECK(!watch_callback_);
CHECK_EQ(watch_type_, WatchType::kNone);
if (IsDialogShown())
return;
base::RunLoop run_loop;
watch_callback_ = run_loop.QuitClosure();
watch_type_ = WatchType::kDialogShown;
run_loop.Run();
}
void MediaRouterUiForTest::WaitForDialogHidden() {
if (!IsDialogShown())
return;
ObserveDialog(WatchType::kDialogHidden);
}
void MediaRouterUiForTest::WaitUntilNoRoutes() {
base::RunLoop run_loop;
NoRoutesObserver no_routes_observer(
MediaRouterFactory::GetApiForBrowserContext(
web_contents_->GetBrowserContext()),
run_loop.QuitClosure());
run_loop.Run();
}
MediaRoute::Id MediaRouterUiForTest::GetRouteIdForSink(
const std::string& sink_name) const {
CastDialogSinkButton* sink_button = GetSinkButton(sink_name);
if (!sink_button->sink().route) {
NOTREACHED() << "Route not found for sink " << sink_name;
return "";
}
return sink_button->sink().route->media_route_id();
}
std::string MediaRouterUiForTest::GetStatusTextForSink(
const std::string& sink_name) const {
CastDialogSinkButton* sink_button = GetSinkButton(sink_name);
return base::UTF16ToUTF8(sink_button->sink().status_text);
}
std::string MediaRouterUiForTest::GetIssueTextForSink(
const std::string& sink_name) const {
CastDialogSinkButton* sink_button = GetSinkButton(sink_name);
if (!sink_button->sink().issue) {
NOTREACHED() << "Issue not found for sink " << sink_name;
return "";
}
return sink_button->sink().issue->info().title;
}
void MediaRouterUiForTest::SetLocalFile(const GURL& file_url) {
dialog_controller_->ui()->set_media_router_file_dialog_for_test(
std::make_unique<TestMediaRouterFileDialog>(dialog_controller_->ui(),
file_url));
}
void MediaRouterUiForTest::SetLocalFileSelectionIssue(const IssueInfo& issue) {
dialog_controller_->ui()->set_media_router_file_dialog_for_test(
std::make_unique<TestFailMediaRouterFileDialog>(dialog_controller_->ui(),
issue));
}
MediaRouterUiForTest::MediaRouterUiForTest(content::WebContents* web_contents)
: web_contents_(web_contents),
dialog_controller_(
MediaRouterDialogControllerViews::GetOrCreateForWebContents(
web_contents)),
weak_factory_(this) {
dialog_controller_->SetDialogCreationCallbackForTesting(base::BindRepeating(
&MediaRouterUiForTest::OnDialogCreated, weak_factory_.GetWeakPtr()));
}
void MediaRouterUiForTest::OnDialogModelUpdated(CastDialogView* dialog_view) {
if (!watch_callback_ || watch_type_ == WatchType::kDialogShown ||
watch_type_ == WatchType::kDialogHidden) {
return;
}
const std::vector<CastDialogSinkButton*>& sink_buttons =
dialog_view->sink_buttons_for_test();
if (std::find_if(sink_buttons.begin(), sink_buttons.end(),
[&, this](CastDialogSinkButton* sink_button) {
switch (watch_type_) {
case WatchType::kSink:
return sink_button->sink().friendly_name ==
base::UTF8ToUTF16(*watch_sink_name_);
case WatchType::kSinkAvailable:
return sink_button->sink().friendly_name ==
base::UTF8ToUTF16(*watch_sink_name_) &&
sink_button->sink().state ==
UIMediaSinkState::AVAILABLE &&
sink_button->enabled();
case WatchType::kAnyIssue:
return sink_button->sink().issue.has_value();
case WatchType::kAnyRoute:
return sink_button->sink().route.has_value();
case WatchType::kNone:
case WatchType::kDialogShown:
case WatchType::kDialogHidden:
NOTREACHED() << "Invalid WatchType";
return false;
}
}) != sink_buttons.end()) {
std::move(*watch_callback_).Run();
watch_callback_.reset();
watch_sink_name_.reset();
watch_type_ = WatchType::kNone;
dialog_view->RemoveObserver(this);
}
}
void MediaRouterUiForTest::OnDialogWillClose(CastDialogView* dialog_view) {
if (watch_type_ == WatchType::kDialogHidden) {
std::move(*watch_callback_).Run();
watch_callback_.reset();
watch_type_ = WatchType::kNone;
}
CHECK(!watch_callback_);
if (dialog_view)
dialog_view->RemoveObserver(this);
}
void MediaRouterUiForTest::OnDialogCreated() {
if (watch_type_ == WatchType::kDialogShown) {
std::move(*watch_callback_).Run();
watch_callback_.reset();
watch_type_ = WatchType::kNone;
}
CastDialogView::GetInstance()->KeepShownForTesting();
}
CastDialogSinkButton* MediaRouterUiForTest::GetSinkButton(
const std::string& sink_name) const {
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
const std::vector<CastDialogSinkButton*>& sink_buttons =
dialog_view->sink_buttons_for_test();
auto it = std::find_if(sink_buttons.begin(), sink_buttons.end(),
[sink_name](CastDialogSinkButton* sink_button) {
return sink_button->sink().friendly_name ==
base::UTF8ToUTF16(sink_name);
});
if (it == sink_buttons.end()) {
NOTREACHED() << "Sink button not found for sink: " << sink_name;
return nullptr;
} else {
return *it;
}
}
void MediaRouterUiForTest::ObserveDialog(
WatchType watch_type,
base::Optional<std::string> sink_name) {
CHECK(!watch_sink_name_);
CHECK(!watch_callback_);
CHECK_EQ(watch_type_, WatchType::kNone);
base::RunLoop run_loop;
watch_sink_name_ = std::move(sink_name);
watch_callback_ = run_loop.QuitClosure();
watch_type_ = watch_type;
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
dialog_view->AddObserver(this);
// Check if the current dialog state already meets the condition that we are
// waiting for.
OnDialogModelUpdated(dialog_view);
run_loop.Run();
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(MediaRouterUiForTest)
} // namespace media_router