Commit 2128f094 authored by Takumi Fujimoto's avatar Takumi Fujimoto Committed by Commit Bot

[Harmony Cast Dialog] Disable sinks incompatible with the selected source

In a future CL, we will be removing the "Cast" button in the dialog
and enabling the user to start casting just by clicking on a sink. This
CL is in preparation for that.

Currently, the source list only shows sources supported by the currently
selected sink. This CL makes the list show all the sources. When a source
is selected, we disable buttons for sinks that do not support the source.

The visual design for disabled sinks is TBD; the icon/text are greyed out
for now.

Bug: 872509

Change-Id: I5ba719d08dd85e2b8915da22d0a2b22988b4f384
Reviewed-on: https://chromium-review.googlesource.com/1166341
Commit-Queue: Takumi Fujimoto <takumif@chromium.org>
Reviewed-by: default avatarDerek Cheng <imcheng@chromium.org>
Reviewed-by: default avatarBret Sepulveda <bsep@chromium.org>
Cr-Commit-Position: refs/heads/master@{#583492}
parent b0e391b3
...@@ -69,6 +69,7 @@ HoverButton::HoverButton(views::ButtonListener* button_listener, ...@@ -69,6 +69,7 @@ HoverButton::HoverButton(views::ButtonListener* button_listener,
: views::MenuButton(text, this, false), : views::MenuButton(text, this, false),
title_(nullptr), title_(nullptr),
subtitle_(nullptr), subtitle_(nullptr),
icon_view_(nullptr),
secondary_icon_view_(nullptr), secondary_icon_view_(nullptr),
listener_(button_listener) { listener_(button_listener) {
SetFocusBehavior(FocusBehavior::ALWAYS); SetFocusBehavior(FocusBehavior::ALWAYS);
...@@ -142,6 +143,7 @@ HoverButton::HoverButton(views::ButtonListener* button_listener, ...@@ -142,6 +143,7 @@ HoverButton::HoverButton(views::ButtonListener* button_listener,
taken_width_ = GetInsets().width() + icon_view->GetPreferredSize().width() + taken_width_ = GetInsets().width() + icon_view->GetPreferredSize().width() +
icon_label_spacing; icon_label_spacing;
icon_view_ = icon_view.get();
// Make sure hovering over the icon also hovers the |HoverButton|. // Make sure hovering over the icon also hovers the |HoverButton|.
icon_view->set_can_process_events_within_subtree(false); icon_view->set_can_process_events_within_subtree(false);
// Don't cover |icon_view| when the ink drops are being painted. |MenuButton| // Don't cover |icon_view| when the ink drops are being painted. |MenuButton|
......
...@@ -107,9 +107,13 @@ class HoverButton : public views::MenuButton, public views::MenuButtonListener { ...@@ -107,9 +107,13 @@ class HoverButton : public views::MenuButton, public views::MenuButtonListener {
views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override; views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
void OnBoundsChanged(const gfx::Rect& previous_bounds) override; void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
views::StyledLabel* title() const { return title_; }
views::View* icon_view() const { return icon_view_; }
private: private:
views::StyledLabel* title_; views::StyledLabel* title_;
views::Label* subtitle_; views::Label* subtitle_;
views::View* icon_view_;
views::View* secondary_icon_view_; views::View* secondary_icon_view_;
// The horizontal space the padding and icon take up. Used for calculating the // The horizontal space the padding and icon take up. Used for calculating the
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "ui/gfx/color_palette.h" #include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h" #include "ui/gfx/paint_vector_icon.h"
#include "ui/views/animation/ink_drop_impl.h" #include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/throbber.h" #include "ui/views/controls/throbber.h"
#include "ui/views/vector_icons.h" #include "ui/views/vector_icons.h"
...@@ -25,7 +26,7 @@ namespace media_router { ...@@ -25,7 +26,7 @@ namespace media_router {
namespace { namespace {
gfx::ImageSkia CreateSinkIcon(SinkIconType icon_type) { gfx::ImageSkia CreateSinkIcon(SinkIconType icon_type, bool enabled = true) {
const gfx::VectorIcon* vector_icon; const gfx::VectorIcon* vector_icon;
switch (icon_type) { switch (icon_type) {
case SinkIconType::CAST_AUDIO_GROUP: case SinkIconType::CAST_AUDIO_GROUP:
...@@ -56,9 +57,13 @@ gfx::ImageSkia CreateSinkIcon(SinkIconType icon_type) { ...@@ -56,9 +57,13 @@ gfx::ImageSkia CreateSinkIcon(SinkIconType icon_type) {
vector_icon = &kTvIcon; vector_icon = &kTvIcon;
break; break;
} }
return gfx::CreateVectorIcon(*vector_icon, SkColor icon_color = enabled ? gfx::kChromeIconGrey : gfx::kGoogleGrey500;
CastDialogSinkButton::kPrimaryIconSize, return gfx::CreateVectorIcon(
gfx::kChromeIconGrey); *vector_icon, CastDialogSinkButton::kPrimaryIconSize, icon_color);
}
gfx::ImageSkia CreateDisabledSinkIcon(SinkIconType icon_type) {
return CreateSinkIcon(icon_type, false);
} }
std::unique_ptr<views::View> CreatePrimaryIconForSink(const UIMediaSink& sink) { std::unique_ptr<views::View> CreatePrimaryIconForSink(const UIMediaSink& sink) {
...@@ -160,6 +165,26 @@ bool CastDialogSinkButton::OnKeyPressed(const ui::KeyEvent& event) { ...@@ -160,6 +165,26 @@ bool CastDialogSinkButton::OnKeyPressed(const ui::KeyEvent& event) {
return handled_event; return handled_event;
} }
void CastDialogSinkButton::OnEnabledChanged() {
HoverButton::OnEnabledChanged();
SkColor background_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ProminentButtonColor);
if (enabled()) {
SetTitleTextStyle(views::style::STYLE_PRIMARY, background_color);
if (sink_.state == UIMediaSinkState::AVAILABLE) {
static_cast<views::ImageView*>(icon_view())
->SetImage(CreateSinkIcon(sink_.icon_type));
}
} else {
SetTitleTextStyle(views::style::STYLE_DISABLED, background_color);
if (sink_.state == UIMediaSinkState::AVAILABLE) {
static_cast<views::ImageView*>(icon_view())
->SetImage(CreateDisabledSinkIcon(sink_.icon_type));
}
}
title()->Layout();
}
std::unique_ptr<views::InkDrop> CastDialogSinkButton::CreateInkDrop() { std::unique_ptr<views::InkDrop> CastDialogSinkButton::CreateInkDrop() {
auto ink_drop = HoverButton::CreateInkDrop(); auto ink_drop = HoverButton::CreateInkDrop();
// Without overriding this value, the ink drop would fade in (as opposed to // Without overriding this value, the ink drop would fade in (as opposed to
......
...@@ -38,6 +38,9 @@ class CastDialogSinkButton : public HoverButton { ...@@ -38,6 +38,9 @@ class CastDialogSinkButton : public HoverButton {
bool OnMousePressed(const ui::MouseEvent& event) override; bool OnMousePressed(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override; void OnMouseReleased(const ui::MouseEvent& event) override;
bool OnKeyPressed(const ui::KeyEvent& event) override; bool OnKeyPressed(const ui::KeyEvent& event) override;
void OnEnabledChanged() override;
// views::InkDropHostView:
std::unique_ptr<views::InkDrop> CreateInkDrop() override; std::unique_ptr<views::InkDrop> CreateInkDrop() override;
// Returns the text that should be shown on the main action button of the Cast // Returns the text that should be shown on the main action button of the Cast
......
...@@ -48,15 +48,6 @@ constexpr int kAlternativeSourceButtonId = -1; ...@@ -48,15 +48,6 @@ constexpr int kAlternativeSourceButtonId = -1;
// presenting and mirroring a tab. // presenting and mirroring a tab.
constexpr int kTabSource = PRESENTATION | TAB_MIRROR; constexpr int kTabSource = PRESENTATION | TAB_MIRROR;
bool SupportsTabSource(const UIMediaSink& sink) {
return base::ContainsKey(sink.cast_modes, PRESENTATION) ||
base::ContainsKey(sink.cast_modes, TAB_MIRROR);
}
bool SupportsDesktopSource(const UIMediaSink& sink) {
return base::ContainsKey(sink.cast_modes, DESKTOP_MIRROR);
}
} // namespace } // namespace
// static // static
...@@ -142,15 +133,12 @@ bool CastDialogView::Accept() { ...@@ -142,15 +133,12 @@ bool CastDialogView::Accept() {
controller_->StopCasting(sink.route_id); controller_->StopCasting(sink.route_id);
metrics_.OnStopCasting(); metrics_.OnStopCasting();
} else { } else {
// Go through cast modes in the order of preference to find one that is base::Optional<MediaCastMode> cast_mode = GetCastModeToUse(sink);
// supported and selected. // TODO(takumif): Once we allow casting by clicking on a sink button,
for (MediaCastMode cast_mode : {PRESENTATION, TAB_MIRROR, DESKTOP_MIRROR}) { // |cast_mode| should always be set, so no check would be necessary.
if ((cast_mode & selected_source_) && if (cast_mode) {
base::ContainsKey(sink.cast_modes, cast_mode)) { controller_->StartCasting(sink.id, cast_mode.value());
controller_->StartCasting(sink.id, cast_mode); metrics_.OnStartCasting(base::Time::Now(), selected_sink_index_);
metrics_.OnStartCasting(base::Time::Now(), selected_sink_index_);
break;
}
} }
} }
return false; return false;
...@@ -164,6 +152,8 @@ void CastDialogView::OnModelUpdated(const CastDialogModel& model) { ...@@ -164,6 +152,8 @@ void CastDialogView::OnModelUpdated(const CastDialogModel& model) {
if (model.media_sinks().empty()) { if (model.media_sinks().empty()) {
scroll_position_ = 0; scroll_position_ = 0;
ShowNoSinksView(); ShowNoSinksView();
if (sources_button_)
sources_button_->SetEnabled(false);
} else { } else {
// If |sink_buttons_| is empty, the sink list was empty before this update. // If |sink_buttons_| is empty, the sink list was empty before this update.
// In that case, select the first active sink, so that its session can be // In that case, select the first active sink, so that its session can be
...@@ -177,6 +167,9 @@ void CastDialogView::OnModelUpdated(const CastDialogModel& model) { ...@@ -177,6 +167,9 @@ void CastDialogView::OnModelUpdated(const CastDialogModel& model) {
PopulateScrollView(model.media_sinks()); PopulateScrollView(model.media_sinks());
RestoreSinkListState(); RestoreSinkListState();
metrics_.OnSinksLoaded(base::Time::Now()); metrics_.OnSinksLoaded(base::Time::Now());
if (sources_button_)
sources_button_->SetEnabled(true);
DisableUnsupportedSinks();
} }
dialog_title_ = model.dialog_header(); dialog_title_ = model.dialog_header();
MaybeSizeToContents(); MaybeSizeToContents();
...@@ -218,6 +211,7 @@ bool CastDialogView::IsCommandIdEnabled(int command_id) const { ...@@ -218,6 +211,7 @@ 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; selected_source_ = command_id;
DisableUnsupportedSinks();
metrics_.OnCastModeSelected(); metrics_.OnCastModeSelected();
} }
...@@ -339,14 +333,11 @@ void CastDialogView::PopulateScrollView(const std::vector<UIMediaSink>& sinks) { ...@@ -339,14 +333,11 @@ void CastDialogView::PopulateScrollView(const std::vector<UIMediaSink>& sinks) {
} }
void CastDialogView::ShowSourcesMenu() { void CastDialogView::ShowSourcesMenu() {
sources_menu_model_ = std::make_unique<ui::SimpleMenuModel>(this); if (!sources_menu_model_) {
const UIMediaSink& sink = GetSelectedSink(); sources_menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
if (SupportsTabSource(sink)) {
sources_menu_model_->AddCheckItemWithStringId( sources_menu_model_->AddCheckItemWithStringId(
kTabSource, IDS_MEDIA_ROUTER_TAB_MIRROR_CAST_MODE); kTabSource, IDS_MEDIA_ROUTER_TAB_MIRROR_CAST_MODE);
}
if (SupportsDesktopSource(sink)) {
sources_menu_model_->AddCheckItemWithStringId( sources_menu_model_->AddCheckItemWithStringId(
DESKTOP_MIRROR, IDS_MEDIA_ROUTER_DESKTOP_MIRROR_CAST_MODE); DESKTOP_MIRROR, IDS_MEDIA_ROUTER_DESKTOP_MIRROR_CAST_MODE);
} }
...@@ -359,16 +350,6 @@ void CastDialogView::ShowSourcesMenu() { ...@@ -359,16 +350,6 @@ void CastDialogView::ShowSourcesMenu() {
ui::MENU_SOURCE_MOUSE); ui::MENU_SOURCE_MOUSE);
} }
void CastDialogView::UpdateSourcesMenu(const UIMediaSink& sink) {
bool supports_desktop_source = SupportsDesktopSource(sink);
// If desktop mirroring is supported, show the sources menu button so that
// the user can switch sources.
if (sources_button_)
sources_button_->SetEnabled(supports_desktop_source);
if (!supports_desktop_source && selected_source_ == DESKTOP_MIRROR)
selected_source_ = kTabSource;
}
void CastDialogView::SelectSinkAtIndex(size_t index) { void CastDialogView::SelectSinkAtIndex(size_t index) {
if (selected_sink_index_ != index && if (selected_sink_index_ != index &&
selected_sink_index_ < sink_buttons_.size()) { selected_sink_index_ < sink_buttons_.size()) {
...@@ -378,7 +359,6 @@ void CastDialogView::SelectSinkAtIndex(size_t index) { ...@@ -378,7 +359,6 @@ void CastDialogView::SelectSinkAtIndex(size_t index) {
selected_button->SetSelected(true); selected_button->SetSelected(true);
selected_sink_index_ = index; selected_sink_index_ = index;
UpdateSourcesMenu(selected_button->sink());
// Update the text on the main action button. // Update the text on the main action button.
DialogModelChanged(); DialogModelChanged();
} }
...@@ -394,6 +374,25 @@ void CastDialogView::MaybeSizeToContents() { ...@@ -394,6 +374,25 @@ void CastDialogView::MaybeSizeToContents() {
SizeToContents(); SizeToContents();
} }
base::Optional<MediaCastMode> CastDialogView::GetCastModeToUse(
const UIMediaSink& sink) const {
// Go through cast modes in the order of preference to find one that is
// supported and selected.
for (MediaCastMode cast_mode : {PRESENTATION, TAB_MIRROR, DESKTOP_MIRROR}) {
if ((cast_mode & selected_source_) &&
base::ContainsKey(sink.cast_modes, cast_mode)) {
return cast_mode;
}
}
return base::nullopt;
}
void CastDialogView::DisableUnsupportedSinks() {
for (CastDialogSinkButton* sink_button : sink_buttons_) {
sink_button->SetEnabled(GetCastModeToUse(sink_button->sink()).has_value());
}
}
void CastDialogView::RecordSinkCountWithDelay() { void CastDialogView::RecordSinkCountWithDelay() {
// Record the number of sinks after three seconds. This is consistent with the // Record the number of sinks after three seconds. This is consistent with the
// WebUI dialog. // WebUI dialog.
......
...@@ -138,15 +138,20 @@ class CastDialogView : public views::BubbleDialogDelegateView, ...@@ -138,15 +138,20 @@ 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();
// Populates the sources menu with the sources supported by |sink|.
void UpdateSourcesMenu(const UIMediaSink& sink);
void SelectSinkAtIndex(size_t index); void SelectSinkAtIndex(size_t index);
const UIMediaSink& GetSelectedSink() const; const UIMediaSink& GetSelectedSink() const;
void MaybeSizeToContents(); void MaybeSizeToContents();
// Returns the cast mode that is selected in the sources menu and supported by
// |sink|. Returns nullopt if no such cast mode exists.
base::Optional<MediaCastMode> GetCastModeToUse(const UIMediaSink& sink) const;
// Disables sink buttons for sinks that do not support the currently selected
// source.
void DisableUnsupportedSinks();
// Posts a delayed task to record the number of sinks shown with the metrics // Posts a delayed task to record the number of sinks shown with the metrics
// recorder. // recorder.
void RecordSinkCountWithDelay(); void RecordSinkCountWithDelay();
......
...@@ -113,13 +113,17 @@ class CastDialogViewTest : public ChromeViewsTestBase { ...@@ -113,13 +113,17 @@ class CastDialogViewTest : public ChromeViewsTestBase {
void SelectSinkAtIndex(int index) { void SelectSinkAtIndex(int index) {
ui::MouseEvent mouse_event(ui::ET_MOUSE_PRESSED, gfx::Point(0, 0), ui::MouseEvent mouse_event(ui::ET_MOUSE_PRESSED, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0); gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);
dialog_->ButtonPressed(dialog_->sink_buttons_for_test()[1], mouse_event); dialog_->ButtonPressed(sink_buttons().at(index), mouse_event);
} }
size_t selected_sink_index() { size_t selected_sink_index() {
return dialog_->selected_sink_index_for_test(); return dialog_->selected_sink_index_for_test();
} }
const std::vector<CastDialogSinkButton*>& sink_buttons() {
return dialog_->sink_buttons_for_test();
}
views::ScrollView* scroll_view() { return dialog_->scroll_view_for_test(); } views::ScrollView* scroll_view() { return dialog_->scroll_view_for_test(); }
views::View* no_sinks_view() { return dialog_->no_sinks_view_for_test(); } views::View* no_sinks_view() { return dialog_->no_sinks_view_for_test(); }
...@@ -226,7 +230,7 @@ TEST_F(CastDialogViewTest, UpdateModel) { ...@@ -226,7 +230,7 @@ TEST_F(CastDialogViewTest, UpdateModel) {
dialog_->Accept(); dialog_->Accept();
} }
TEST_F(CastDialogViewTest, ShowAlternativeSources) { TEST_F(CastDialogViewTest, ShowSourcesMenu) {
std::vector<UIMediaSink> media_sinks = {CreateAvailableSink()}; std::vector<UIMediaSink> media_sinks = {CreateAvailableSink()};
media_sinks[0].cast_modes = {TAB_MIRROR, PRESENTATION, DESKTOP_MIRROR}; media_sinks[0].cast_modes = {TAB_MIRROR, PRESENTATION, DESKTOP_MIRROR};
CastDialogModel model = CreateModelWithSinks(media_sinks); CastDialogModel model = CreateModelWithSinks(media_sinks);
...@@ -239,10 +243,8 @@ TEST_F(CastDialogViewTest, ShowAlternativeSources) { ...@@ -239,10 +243,8 @@ TEST_F(CastDialogViewTest, ShowAlternativeSources) {
EXPECT_EQ(PRESENTATION | TAB_MIRROR, sources_menu_model()->GetCommandIdAt(0)); EXPECT_EQ(PRESENTATION | TAB_MIRROR, sources_menu_model()->GetCommandIdAt(0));
EXPECT_EQ(DESKTOP_MIRROR, sources_menu_model()->GetCommandIdAt(1)); EXPECT_EQ(DESKTOP_MIRROR, sources_menu_model()->GetCommandIdAt(1));
// When there are no alternative sources, the sources button should be // When there are no sinks, the sources button should be disabled.
// disabled. model.set_media_sinks({});
media_sinks[0].cast_modes = {TAB_MIRROR};
model.set_media_sinks(std::move(media_sinks));
dialog_->OnModelUpdated(model); dialog_->OnModelUpdated(model);
EXPECT_FALSE(sources_button()->enabled()); EXPECT_FALSE(sources_button()->enabled());
} }
...@@ -267,19 +269,29 @@ TEST_F(CastDialogViewTest, CastToAlternativeSources) { ...@@ -267,19 +269,29 @@ TEST_F(CastDialogViewTest, CastToAlternativeSources) {
dialog_->Accept(); dialog_->Accept();
} }
TEST_F(CastDialogViewTest, DisableAlternativeSourcesPicker) { TEST_F(CastDialogViewTest, DisableUnsupportedSinks) {
CastDialogModel model; std::vector<UIMediaSink> media_sinks = {CreateAvailableSink(),
CreateAvailableSink()};
media_sinks[1].id = "sink_2";
media_sinks[0].cast_modes = {TAB_MIRROR};
media_sinks[1].cast_modes = {PRESENTATION, DESKTOP_MIRROR};
CastDialogModel model = CreateModelWithSinks(std::move(media_sinks));
InitializeDialogWithModel(model); InitializeDialogWithModel(model);
// The picker should be disabled when there are no sinks.
EXPECT_FALSE(sources_button()->enabled());
std::vector<UIMediaSink> media_sinks = {CreateConnectedSink()}; dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
media_sinks[0].cast_modes = {TAB_MIRROR, PRESENTATION}; EXPECT_EQ(DESKTOP_MIRROR, sources_menu_model()->GetCommandIdAt(1));
model.set_media_sinks(std::move(media_sinks)); sources_menu_model()->ActivatedAt(1);
dialog_->OnModelUpdated(model); // Sink at index 0 doesn't support desktop mirroring, so it should be
// The picker should be disabled if the selected sink doesn't support non-tab // disabled.
// sources. EXPECT_FALSE(sink_buttons().at(0)->enabled());
EXPECT_FALSE(sources_button()->enabled()); EXPECT_TRUE(sink_buttons().at(1)->enabled());
dialog_->ButtonPressed(sources_button(), CreateMouseEvent());
EXPECT_EQ(PRESENTATION | TAB_MIRROR, sources_menu_model()->GetCommandIdAt(0));
sources_menu_model()->ActivatedAt(0);
// Both sinks support tab or presentation casting, so they should be enabled.
EXPECT_TRUE(sink_buttons().at(0)->enabled());
EXPECT_TRUE(sink_buttons().at(1)->enabled());
} }
TEST_F(CastDialogViewTest, ShowNoDeviceView) { TEST_F(CastDialogViewTest, ShowNoDeviceView) {
......
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