Commit 7a07736b authored by Takumi Fujimoto's avatar Takumi Fujimoto Committed by Commit Bot

[Harmony Cast Dialog] Add local files as a source

Add local files as one of the options in the sources dropdown. When it
is selected, a file picker dialog is opened via MediaRouterViewsUI. After
selecting a file, the user can select a sink to start casting.

The dialog has a functionality to close itself on blur, which will be
disabled while a file picker is open, and when the file is opened in a
new tab, to prevent the dialog from closing at those moments.

Bug: 842787
Change-Id: I3e9ebf0b1958c026f418111feb792a9d94fce533
Reviewed-on: https://chromium-review.googlesource.com/c/1299657
Commit-Queue: Takumi Fujimoto <takumif@chromium.org>
Reviewed-by: default avatarmark a. foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#607337}
parent 86dea917
...@@ -5,10 +5,15 @@ ...@@ -5,10 +5,15 @@
#ifndef CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_DIALOG_CONTROLLER_H_ #ifndef CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_DIALOG_CONTROLLER_H_
#define CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_DIALOG_CONTROLLER_H_ #define CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_DIALOG_CONTROLLER_H_
#include "base/callback_forward.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h" #include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/common/media_router/media_route.h" #include "chrome/common/media_router/media_route.h"
#include "chrome/common/media_router/media_sink.h" #include "chrome/common/media_router/media_sink.h"
namespace ui {
struct SelectedFileInfo;
} // namespace ui
namespace media_router { namespace media_router {
class CastDialogModel; class CastDialogModel;
...@@ -42,6 +47,11 @@ class CastDialogController { ...@@ -42,6 +47,11 @@ class CastDialogController {
// Stops casting by terminating the route given by |route_id|. No-op if the ID // Stops casting by terminating the route given by |route_id|. No-op if the ID
// is invalid. // is invalid.
virtual void StopCasting(const MediaRoute::Id& route_id) = 0; virtual void StopCasting(const MediaRoute::Id& route_id) = 0;
// Prompts the user to select a local file to cast. The callback is called
// with the info for the selected file, or nullptr if the user declined.
virtual void ChooseLocalFile(
base::OnceCallback<void(const ui::SelectedFileInfo*)> callback) = 0;
}; };
} // namespace media_router } // namespace media_router
......
...@@ -6,8 +6,10 @@ ...@@ -6,8 +6,10 @@
#include "base/location.h" #include "base/location.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/media/router/media_router_metrics.h" #include "chrome/browser/media/router/media_router_metrics.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/media_router/cast_dialog_controller.h" #include "chrome/browser/ui/media_router/cast_dialog_controller.h"
...@@ -47,10 +49,6 @@ namespace { ...@@ -47,10 +49,6 @@ namespace {
// This value is negative so that it doesn't overlap with a sink index. // This value is negative so that it doesn't overlap with a sink index.
constexpr int kAlternativeSourceButtonId = -1; constexpr int kAlternativeSourceButtonId = -1;
// In the sources menu, we have a single item for "tab", which includes both
// presenting and mirroring a tab.
constexpr int kTabSource = PRESENTATION | TAB_MIRROR;
} // namespace } // namespace
// static // static
...@@ -99,12 +97,20 @@ bool CastDialogView::ShouldShowCloseButton() const { ...@@ -99,12 +97,20 @@ bool CastDialogView::ShouldShowCloseButton() const {
} }
base::string16 CastDialogView::GetWindowTitle() const { base::string16 CastDialogView::GetWindowTitle() const {
// |dialog_title_| may contain the presentation URL origin which is not switch (selected_source_) {
// relevant for non-tab sources. So we override it with the default title for case SourceType::kTab:
// those sources. return dialog_title_;
return selected_source_ == kTabSource case SourceType::kDesktop:
? dialog_title_ // |dialog_title_| may contain the presentation URL origin which is not
: l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_CAST_DIALOG_TITLE); // relevant for the desktop source, so we use the default title string.
return l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_CAST_DIALOG_TITLE);
case SourceType::kLocalFile:
return l10n_util::GetStringFUTF16(IDS_MEDIA_ROUTER_CAST_LOCAL_MEDIA_TITLE,
local_file_name_.value());
default:
NOTREACHED();
return base::string16();
}
} }
int CastDialogView::GetDialogButtons() const { int CastDialogView::GetDialogButtons() const {
...@@ -190,10 +196,18 @@ bool CastDialogView::IsCommandIdEnabled(int command_id) const { ...@@ -190,10 +196,18 @@ bool CastDialogView::IsCommandIdEnabled(int command_id) const {
} }
void CastDialogView::ExecuteCommand(int command_id, int event_flags) { void CastDialogView::ExecuteCommand(int command_id, int event_flags) {
selected_source_ = command_id; // This method is called when the user selects a source in the source picker.
DisableUnsupportedSinks(); if (command_id == SourceType::kLocalFile) {
GetWidget()->UpdateWindowTitle(); // When the file picker dialog opens, the Cast dialog loses focus. So we
metrics_.OnCastModeSelected(); // must temporarily prevent it from closing when losing focus.
set_close_on_deactivate(false);
controller_->ChooseLocalFile(base::BindOnce(
&CastDialogView::OnFilePickerClosed, weak_factory_.GetWeakPtr()));
} else {
if (local_file_name_)
local_file_name_.reset();
SelectSource(static_cast<SourceType>(command_id));
}
} }
// static // static
...@@ -217,7 +231,7 @@ CastDialogView::CastDialogView(views::View* anchor_view, ...@@ -217,7 +231,7 @@ CastDialogView::CastDialogView(views::View* anchor_view,
Browser* browser, Browser* browser,
const base::Time& start_time) const base::Time& start_time)
: BubbleDialogDelegateView(anchor_view, anchor_position), : BubbleDialogDelegateView(anchor_view, anchor_position),
selected_source_(kTabSource), selected_source_(SourceType::kTab),
controller_(controller), controller_(controller),
browser_(browser), browser_(browser),
metrics_(start_time), metrics_(start_time),
...@@ -322,9 +336,11 @@ void CastDialogView::ShowSourcesMenu() { ...@@ -322,9 +336,11 @@ void CastDialogView::ShowSourcesMenu() {
sources_menu_model_ = std::make_unique<ui::SimpleMenuModel>(this); sources_menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
sources_menu_model_->AddCheckItemWithStringId( sources_menu_model_->AddCheckItemWithStringId(
kTabSource, IDS_MEDIA_ROUTER_TAB_MIRROR_CAST_MODE); SourceType::kTab, IDS_MEDIA_ROUTER_TAB_MIRROR_CAST_MODE);
sources_menu_model_->AddCheckItemWithStringId( sources_menu_model_->AddCheckItemWithStringId(
DESKTOP_MIRROR, IDS_MEDIA_ROUTER_DESKTOP_MIRROR_CAST_MODE); SourceType::kDesktop, IDS_MEDIA_ROUTER_DESKTOP_MIRROR_CAST_MODE);
sources_menu_model_->AddCheckItemWithStringId(
SourceType::kLocalFile, IDS_MEDIA_ROUTER_LOCAL_FILE_CAST_MODE);
} }
sources_menu_runner_ = std::make_unique<views::MenuRunner>( sources_menu_runner_ = std::make_unique<views::MenuRunner>(
...@@ -335,6 +351,13 @@ void CastDialogView::ShowSourcesMenu() { ...@@ -335,6 +351,13 @@ void CastDialogView::ShowSourcesMenu() {
ui::MENU_SOURCE_MOUSE); ui::MENU_SOURCE_MOUSE);
} }
void CastDialogView::SelectSource(SourceType source) {
selected_source_ = source;
DisableUnsupportedSinks();
GetWidget()->UpdateWindowTitle();
metrics_.OnCastModeSelected();
}
void CastDialogView::SinkPressed(size_t index) { void CastDialogView::SinkPressed(size_t index) {
if (!controller_) if (!controller_)
return; return;
...@@ -344,7 +367,16 @@ void CastDialogView::SinkPressed(size_t index) { ...@@ -344,7 +367,16 @@ void CastDialogView::SinkPressed(size_t index) {
if (sink.route_id.empty()) { if (sink.route_id.empty()) {
base::Optional<MediaCastMode> cast_mode = GetCastModeToUse(sink); base::Optional<MediaCastMode> cast_mode = GetCastModeToUse(sink);
if (cast_mode) { if (cast_mode) {
// Starting local file casting may open a new tab synchronously on the UI
// thread, which deactivates the dialog. So we must prevent it from
// closing and getting destroyed.
if (cast_mode == LOCAL_FILE)
set_close_on_deactivate(false);
controller_->StartCasting(sink.id, cast_mode.value()); controller_->StartCasting(sink.id, cast_mode.value());
// Re-enable close on deactivate so the user can click elsewhere to close
// the dialog.
if (cast_mode == LOCAL_FILE)
set_close_on_deactivate(true);
metrics_.OnStartCasting(base::Time::Now(), index); metrics_.OnStartCasting(base::Time::Now(), index);
} }
} else { } else {
...@@ -363,11 +395,21 @@ base::Optional<MediaCastMode> CastDialogView::GetCastModeToUse( ...@@ -363,11 +395,21 @@ base::Optional<MediaCastMode> CastDialogView::GetCastModeToUse(
const UIMediaSink& sink) const { const UIMediaSink& sink) const {
// Go through cast modes in the order of preference to find one that is // Go through cast modes in the order of preference to find one that is
// supported and selected. // supported and selected.
for (MediaCastMode cast_mode : {PRESENTATION, TAB_MIRROR, DESKTOP_MIRROR}) { switch (selected_source_) {
if ((cast_mode & selected_source_) && case SourceType::kTab:
base::ContainsKey(sink.cast_modes, cast_mode)) { if (base::ContainsKey(sink.cast_modes, PRESENTATION))
return cast_mode; return base::make_optional<MediaCastMode>(PRESENTATION);
} if (base::ContainsKey(sink.cast_modes, TAB_MIRROR))
return base::make_optional<MediaCastMode>(TAB_MIRROR);
break;
case SourceType::kDesktop:
if (base::ContainsKey(sink.cast_modes, DESKTOP_MIRROR))
return base::make_optional<MediaCastMode>(DESKTOP_MIRROR);
break;
case SourceType::kLocalFile:
if (base::ContainsKey(sink.cast_modes, LOCAL_FILE))
return base::make_optional<MediaCastMode>(LOCAL_FILE);
break;
} }
return base::nullopt; return base::nullopt;
} }
...@@ -393,6 +435,19 @@ void CastDialogView::RecordSinkCount() { ...@@ -393,6 +435,19 @@ void CastDialogView::RecordSinkCount() {
metrics_.OnRecordSinkCount(sink_buttons_.size()); metrics_.OnRecordSinkCount(sink_buttons_.size());
} }
void CastDialogView::OnFilePickerClosed(const ui::SelectedFileInfo* file_info) {
// Re-enable the setting to close the dialog when it loses focus.
set_close_on_deactivate(true);
if (file_info) {
#if defined(OS_WIN)
local_file_name_ = file_info->display_name;
#else
local_file_name_ = base::UTF8ToUTF16(file_info->display_name);
#endif // defined(OS_WIN)
SelectSource(SourceType::kLocalFile);
}
}
// static // static
CastDialogView* CastDialogView::instance_ = nullptr; CastDialogView* CastDialogView::instance_ = nullptr;
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "chrome/browser/ui/media_router/cast_dialog_controller.h" #include "chrome/browser/ui/media_router/cast_dialog_controller.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_metrics.h" #include "chrome/browser/ui/views/media_router/cast_dialog_metrics.h"
#include "ui/base/models/simple_menu_model.h" #include "ui/base/models/simple_menu_model.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/button.h" #include "ui/views/controls/button/button.h"
...@@ -101,7 +102,13 @@ class CastDialogView : public views::BubbleDialogDelegateView, ...@@ -101,7 +102,13 @@ class CastDialogView : public views::BubbleDialogDelegateView,
private: private:
friend class CastDialogViewTest; friend class CastDialogViewTest;
FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, CancelLocalFileSelection);
FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, CastLocalFile);
FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, DisableUnsupportedSinks);
FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, ShowAndHideDialog); FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, ShowAndHideDialog);
FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, ShowSourcesMenu);
enum SourceType { kTab, kDesktop, kLocalFile };
// Instantiates and shows the singleton dialog. The dialog must not be // Instantiates and shows the singleton dialog. The dialog must not be
// currently shown. // currently shown.
...@@ -134,6 +141,10 @@ class CastDialogView : public views::BubbleDialogDelegateView, ...@@ -134,6 +141,10 @@ class CastDialogView : public views::BubbleDialogDelegateView,
// Shows the sources menu that allows the user to choose a source to cast. // Shows the sources menu that allows the user to choose a source to cast.
void ShowSourcesMenu(); void ShowSourcesMenu();
// Stores |source| as the source to be used when user selects a sink to start
// casting, and updates the UI to reflect the selection.
void SelectSource(SourceType source);
void SinkPressed(size_t index); void SinkPressed(size_t index);
void MaybeSizeToContents(); void MaybeSizeToContents();
...@@ -153,6 +164,9 @@ class CastDialogView : public views::BubbleDialogDelegateView, ...@@ -153,6 +164,9 @@ class CastDialogView : public views::BubbleDialogDelegateView,
// Records the number of sinks shown with the metrics recorder. // Records the number of sinks shown with the metrics recorder.
void RecordSinkCount(); void RecordSinkCount();
// Sets local file as the selected source if |file_info| is not null.
void OnFilePickerClosed(const ui::SelectedFileInfo* file_info);
// The singleton dialog instance. This is a nullptr when a dialog is not // The singleton dialog instance. This is a nullptr when a dialog is not
// shown. // shown.
static CastDialogView* instance_; static CastDialogView* instance_;
...@@ -163,7 +177,7 @@ class CastDialogView : public views::BubbleDialogDelegateView, ...@@ -163,7 +177,7 @@ class CastDialogView : public views::BubbleDialogDelegateView,
// The source selected in the sources menu. This defaults to "tab" // The source selected in the sources menu. This defaults to "tab"
// (presentation or tab mirroring). "Tab" is represented by a single item in // (presentation or tab mirroring). "Tab" is represented by a single item in
// the sources menu. // the sources menu.
int selected_source_; SourceType selected_source_ = SourceType::kTab;
// Contains references to sink buttons in the order they appear. // Contains references to sink buttons in the order they appear.
std::vector<CastDialogSinkButton*> sink_buttons_; std::vector<CastDialogSinkButton*> sink_buttons_;
...@@ -195,6 +209,9 @@ class CastDialogView : public views::BubbleDialogDelegateView, ...@@ -195,6 +209,9 @@ class CastDialogView : public views::BubbleDialogDelegateView,
// multiple sinks at the same time, the last activated sink is used. // multiple sinks at the same time, the last activated sink is used.
base::Optional<size_t> selected_sink_index_; base::Optional<size_t> selected_sink_index_;
// This value is set if the user has chosen a local file to cast.
base::Optional<base::string16> local_file_name_;
base::WeakPtrFactory<CastDialogView> weak_factory_; base::WeakPtrFactory<CastDialogView> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(CastDialogView); DISALLOW_COPY_AND_ASSIGN(CastDialogView);
......
...@@ -76,6 +76,9 @@ class MockCastDialogController : public CastDialogController { ...@@ -76,6 +76,9 @@ class MockCastDialogController : public CastDialogController {
MOCK_METHOD2(StartCasting, MOCK_METHOD2(StartCasting,
void(const std::string& sink_id, MediaCastMode cast_mode)); void(const std::string& sink_id, MediaCastMode cast_mode));
MOCK_METHOD1(StopCasting, void(const std::string& route_id)); MOCK_METHOD1(StopCasting, void(const std::string& route_id));
MOCK_METHOD1(
ChooseLocalFile,
void(base::OnceCallback<void(const ui::SelectedFileInfo*)> callback));
}; };
class CastDialogViewTest : public ChromeViewsTestBase { class CastDialogViewTest : public ChromeViewsTestBase {
...@@ -205,11 +208,13 @@ TEST_F(CastDialogViewTest, ShowSourcesMenu) { ...@@ -205,11 +208,13 @@ TEST_F(CastDialogViewTest, ShowSourcesMenu) {
InitializeDialogWithModel(model); InitializeDialogWithModel(model);
// Press the button to show the sources menu. // Press the button to show the sources menu.
dialog_->ButtonPressed(sources_button(), CreateMouseEvent()); dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
// The items should be "tab" (includes tab mirroring and presentation) and // The items should be "tab" (includes tab mirroring and presentation),
// "desktop". // "desktop", and "local file".
EXPECT_EQ(2, sources_menu_model()->GetItemCount()); EXPECT_EQ(3, sources_menu_model()->GetItemCount());
EXPECT_EQ(PRESENTATION | TAB_MIRROR, sources_menu_model()->GetCommandIdAt(0)); EXPECT_EQ(CastDialogView::kTab, sources_menu_model()->GetCommandIdAt(0));
EXPECT_EQ(DESKTOP_MIRROR, sources_menu_model()->GetCommandIdAt(1)); EXPECT_EQ(CastDialogView::kDesktop, sources_menu_model()->GetCommandIdAt(1));
EXPECT_EQ(CastDialogView::kLocalFile,
sources_menu_model()->GetCommandIdAt(2));
// When there are no sinks, the sources button should be disabled. // When there are no sinks, the sources button should be disabled.
model.set_media_sinks({}); model.set_media_sinks({});
...@@ -217,14 +222,15 @@ TEST_F(CastDialogViewTest, ShowSourcesMenu) { ...@@ -217,14 +222,15 @@ TEST_F(CastDialogViewTest, ShowSourcesMenu) {
EXPECT_FALSE(sources_button()->enabled()); EXPECT_FALSE(sources_button()->enabled());
} }
TEST_F(CastDialogViewTest, CastToAlternativeSources) { TEST_F(CastDialogViewTest, CastAlternativeSources) {
std::vector<UIMediaSink> media_sinks = {CreateAvailableSink()}; std::vector<UIMediaSink> media_sinks = {CreateAvailableSink()};
media_sinks[0].cast_modes = {TAB_MIRROR, DESKTOP_MIRROR}; media_sinks[0].cast_modes = {TAB_MIRROR, DESKTOP_MIRROR};
CastDialogModel model = CreateModelWithSinks(std::move(media_sinks)); CastDialogModel model = CreateModelWithSinks(std::move(media_sinks));
InitializeDialogWithModel(model); InitializeDialogWithModel(model);
// Press the button to show the sources menu. // Press the button to show the sources menu.
dialog_->ButtonPressed(sources_button(), CreateMouseEvent()); dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
ASSERT_EQ(2, sources_menu_model()->GetItemCount()); // There should be three sources: tab, desktop, and local file.
ASSERT_EQ(3, sources_menu_model()->GetItemCount());
EXPECT_CALL(controller_, StartCasting(model.media_sinks()[0].id, TAB_MIRROR)); EXPECT_CALL(controller_, StartCasting(model.media_sinks()[0].id, TAB_MIRROR));
sources_menu_model()->ActivatedAt(0); sources_menu_model()->ActivatedAt(0);
...@@ -237,6 +243,67 @@ TEST_F(CastDialogViewTest, CastToAlternativeSources) { ...@@ -237,6 +243,67 @@ TEST_F(CastDialogViewTest, CastToAlternativeSources) {
SinkPressedAtIndex(0); SinkPressedAtIndex(0);
} }
TEST_F(CastDialogViewTest, CastLocalFile) {
const std::string file_name = "example.mp4";
const std::string file_path = "path/to/" + file_name;
std::vector<UIMediaSink> media_sinks = {CreateAvailableSink()};
media_sinks[0].cast_modes = {TAB_MIRROR, LOCAL_FILE};
CastDialogModel model = CreateModelWithSinks(std::move(media_sinks));
InitializeDialogWithModel(model);
dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
#if defined(OS_WIN)
ui::SelectedFileInfo file_info{base::FilePath(base::UTF8ToUTF16(file_name)),
base::FilePath(base::UTF8ToUTF16(file_path))};
#else
ui::SelectedFileInfo file_info{base::FilePath(file_name),
base::FilePath(file_path)};
#endif // defined(OS_WIN)
EXPECT_CALL(controller_, ChooseLocalFile(_))
.WillOnce(
[file_info](base::OnceCallback<void(const ui::SelectedFileInfo*)>
file_callback) {
std::move(file_callback).Run(&file_info);
});
ASSERT_EQ(CastDialogView::kLocalFile,
sources_menu_model()->GetCommandIdAt(2));
sources_menu_model()->ActivatedAt(2);
EXPECT_EQ(dialog_->GetWindowTitle(),
l10n_util::GetStringFUTF16(IDS_MEDIA_ROUTER_CAST_LOCAL_MEDIA_TITLE,
base::UTF8ToUTF16(file_name)));
EXPECT_CALL(controller_, StartCasting(model.media_sinks()[0].id, LOCAL_FILE));
SinkPressedAtIndex(0);
}
TEST_F(CastDialogViewTest, CancelLocalFileSelection) {
std::vector<UIMediaSink> media_sinks = {CreateAvailableSink()};
media_sinks[0].cast_modes = {TAB_MIRROR, LOCAL_FILE};
CastDialogModel model = CreateModelWithSinks(std::move(media_sinks));
InitializeDialogWithModel(model);
dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
// The tab source should be selected by default.
ASSERT_EQ(CastDialogView::kTab, sources_menu_model()->GetCommandIdAt(0));
ASSERT_TRUE(sources_menu_model()->IsItemCheckedAt(0));
// Select the local file source, then cancel file selection by passing a
// nullptr into the callback.
EXPECT_CALL(controller_, ChooseLocalFile(_))
.WillOnce(
[](base::OnceCallback<void(const ui::SelectedFileInfo*)>
file_callback) { std::move(file_callback).Run(nullptr); });
ASSERT_EQ(CastDialogView::kLocalFile,
sources_menu_model()->GetCommandIdAt(2));
sources_menu_model()->ActivatedAt(2);
// Since we cancelled file selection, "tab" should still be the selected
// source.
EXPECT_TRUE(sources_menu_model()->IsItemCheckedAt(0));
EXPECT_CALL(controller_, StartCasting(model.media_sinks()[0].id, TAB_MIRROR));
SinkPressedAtIndex(0);
}
TEST_F(CastDialogViewTest, DisableUnsupportedSinks) { TEST_F(CastDialogViewTest, DisableUnsupportedSinks) {
std::vector<UIMediaSink> media_sinks = {CreateAvailableSink(), std::vector<UIMediaSink> media_sinks = {CreateAvailableSink(),
CreateAvailableSink()}; CreateAvailableSink()};
...@@ -247,7 +314,7 @@ TEST_F(CastDialogViewTest, DisableUnsupportedSinks) { ...@@ -247,7 +314,7 @@ TEST_F(CastDialogViewTest, DisableUnsupportedSinks) {
InitializeDialogWithModel(model); InitializeDialogWithModel(model);
dialog_->ButtonPressed(sources_button(), CreateMouseEvent()); dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
EXPECT_EQ(DESKTOP_MIRROR, sources_menu_model()->GetCommandIdAt(1)); EXPECT_EQ(CastDialogView::kDesktop, sources_menu_model()->GetCommandIdAt(1));
sources_menu_model()->ActivatedAt(1); sources_menu_model()->ActivatedAt(1);
// Sink at index 0 doesn't support desktop mirroring, so it should be // Sink at index 0 doesn't support desktop mirroring, so it should be
// disabled. // disabled.
...@@ -255,7 +322,7 @@ TEST_F(CastDialogViewTest, DisableUnsupportedSinks) { ...@@ -255,7 +322,7 @@ TEST_F(CastDialogViewTest, DisableUnsupportedSinks) {
EXPECT_TRUE(sink_buttons().at(1)->enabled()); EXPECT_TRUE(sink_buttons().at(1)->enabled());
dialog_->ButtonPressed(sources_button(), CreateMouseEvent()); dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
EXPECT_EQ(PRESENTATION | TAB_MIRROR, sources_menu_model()->GetCommandIdAt(0)); EXPECT_EQ(CastDialogView::kTab, sources_menu_model()->GetCommandIdAt(0));
sources_menu_model()->ActivatedAt(0); sources_menu_model()->ActivatedAt(0);
// Both sinks support tab or presentation casting, so they should be enabled. // Both sinks support tab or presentation casting, so they should be enabled.
EXPECT_TRUE(sink_buttons().at(0)->enabled()); EXPECT_TRUE(sink_buttons().at(0)->enabled());
......
...@@ -60,13 +60,8 @@ void MediaRouterViewsUI::RemoveObserver( ...@@ -60,13 +60,8 @@ void MediaRouterViewsUI::RemoveObserver(
void MediaRouterViewsUI::StartCasting(const std::string& sink_id, void MediaRouterViewsUI::StartCasting(const std::string& sink_id,
MediaCastMode cast_mode) { MediaCastMode cast_mode) {
if (cast_mode == LOCAL_FILE) { CreateRoute(sink_id, cast_mode);
local_file_sink_id_ = sink_id; UpdateSinks();
OpenFileDialog();
} else {
CreateRoute(sink_id, cast_mode);
UpdateSinks();
}
} }
void MediaRouterViewsUI::StopCasting(const std::string& route_id) { void MediaRouterViewsUI::StopCasting(const std::string& route_id) {
...@@ -77,6 +72,12 @@ void MediaRouterViewsUI::StopCasting(const std::string& route_id) { ...@@ -77,6 +72,12 @@ void MediaRouterViewsUI::StopCasting(const std::string& route_id) {
TerminateRoute(terminating_route_id_.value()); TerminateRoute(terminating_route_id_.value());
} }
void MediaRouterViewsUI::ChooseLocalFile(
base::OnceCallback<void(const ui::SelectedFileInfo*)> callback) {
file_selection_callback_ = std::move(callback);
OpenFileDialog();
}
std::vector<MediaSinkWithCastModes> MediaRouterViewsUI::GetEnabledSinks() std::vector<MediaSinkWithCastModes> MediaRouterViewsUI::GetEnabledSinks()
const { const {
std::vector<MediaSinkWithCastModes> sinks = std::vector<MediaSinkWithCastModes> sinks =
...@@ -196,13 +197,17 @@ void MediaRouterViewsUI::UpdateModelHeader() { ...@@ -196,13 +197,17 @@ void MediaRouterViewsUI::UpdateModelHeader() {
void MediaRouterViewsUI::FileDialogFileSelected( void MediaRouterViewsUI::FileDialogFileSelected(
const ui::SelectedFileInfo& file_info) { const ui::SelectedFileInfo& file_info) {
CreateRoute(local_file_sink_id_.value(), LOCAL_FILE); std::move(file_selection_callback_).Run(&file_info);
local_file_sink_id_.reset();
} }
void MediaRouterViewsUI::FileDialogSelectionFailed(const IssueInfo& issue) { void MediaRouterViewsUI::FileDialogSelectionFailed(const IssueInfo& issue) {
MediaRouterUIBase::FileDialogSelectionFailed(issue); MediaRouterUIBase::FileDialogSelectionFailed(issue);
local_file_sink_id_.reset(); std::move(file_selection_callback_).Run(nullptr);
}
void MediaRouterViewsUI::FileDialogSelectionCanceled() {
MediaRouterUIBase::FileDialogSelectionCanceled();
std::move(file_selection_callback_).Run(nullptr);
} }
} // namespace media_router } // namespace media_router
...@@ -26,6 +26,8 @@ class MediaRouterViewsUI : public MediaRouterUIBase, ...@@ -26,6 +26,8 @@ class MediaRouterViewsUI : public MediaRouterUIBase,
void StartCasting(const std::string& sink_id, void StartCasting(const std::string& sink_id,
MediaCastMode cast_mode) override; MediaCastMode cast_mode) override;
void StopCasting(const std::string& route_id) override; void StopCasting(const std::string& route_id) override;
void ChooseLocalFile(
base::OnceCallback<void(const ui::SelectedFileInfo*)> callback) override;
// MediaRouterUIBase: // MediaRouterUIBase:
std::vector<MediaSinkWithCastModes> GetEnabledSinks() const override; std::vector<MediaSinkWithCastModes> GetEnabledSinks() const override;
...@@ -62,6 +64,7 @@ class MediaRouterViewsUI : public MediaRouterUIBase, ...@@ -62,6 +64,7 @@ class MediaRouterViewsUI : public MediaRouterUIBase,
// MediaRouterFileDialogDelegate: // MediaRouterFileDialogDelegate:
void FileDialogFileSelected(const ui::SelectedFileInfo& file_info) override; void FileDialogFileSelected(const ui::SelectedFileInfo& file_info) override;
void FileDialogSelectionFailed(const IssueInfo& issue) override; void FileDialogSelectionFailed(const IssueInfo& issue) override;
void FileDialogSelectionCanceled() override;
// This value is set whenever there is an outstanding issue. // This value is set whenever there is an outstanding issue.
base::Optional<Issue> issue_; base::Optional<Issue> issue_;
...@@ -81,6 +84,9 @@ class MediaRouterViewsUI : public MediaRouterUIBase, ...@@ -81,6 +84,9 @@ class MediaRouterViewsUI : public MediaRouterUIBase,
// TODO(takumif): CastDialogModel should manage the observers. // TODO(takumif): CastDialogModel should manage the observers.
base::ObserverList<CastDialogController::Observer>::Unchecked observers_; base::ObserverList<CastDialogController::Observer>::Unchecked observers_;
base::OnceCallback<void(const ui::SelectedFileInfo*)>
file_selection_callback_;
DISALLOW_COPY_AND_ASSIGN(MediaRouterViewsUI); DISALLOW_COPY_AND_ASSIGN(MediaRouterViewsUI);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment