blob: 062a9821c9c0422fac63daca2ffd7f0bbc6cd94a [file] [log] [blame]
// Copyright 2015 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/browser/ui/toolbar/media_router_action.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "chrome/browser/media/router/media_router.h"
#include "chrome/browser/media/router/media_router_factory.h"
#include "chrome/browser/media/router/media_router_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/media_router/media_router_dialog_controller_impl_base.h"
#include "chrome/browser/ui/media_router/media_router_ui_service.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
#include "chrome/browser/ui/toolbar/media_router_action_controller.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.h"
#include "chrome/common/media_router/issue.h"
#include "chrome/common/media_router/media_route.h"
#include "chrome/grit/generated_resources.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
using media_router::MediaRouterDialogControllerImplBase;
namespace {
media_router::MediaRouter* GetMediaRouter(Browser* browser) {
return media_router::MediaRouterFactory::GetApiForBrowserContext(
browser->profile());
}
} // namespace
MediaRouterAction::MediaRouterAction(Browser* browser,
ToolbarActionsBar* toolbar_actions_bar)
: media_router::IssuesObserver(GetMediaRouter(browser)->GetIssueManager()),
media_router::MediaRoutesObserver(GetMediaRouter(browser)),
current_icon_(&vector_icons::kMediaRouterIdleIcon),
has_local_display_route_(false),
has_dialog_(false),
delegate_(nullptr),
browser_(browser),
toolbar_actions_bar_(toolbar_actions_bar),
tab_strip_model_observer_(this),
toolbar_actions_bar_observer_(this),
skip_close_overflow_menu_for_testing_(false),
weak_ptr_factory_(this) {
DCHECK(browser_);
DCHECK(toolbar_actions_bar_);
tab_strip_model_observer_.Add(browser_->tab_strip_model());
toolbar_actions_bar_observer_.Add(toolbar_actions_bar_);
IssuesObserver::Init();
}
MediaRouterAction::~MediaRouterAction() {
}
// static
SkColor MediaRouterAction::GetIconColor(const gfx::VectorIcon& icon_id) {
if (&icon_id == &vector_icons::kMediaRouterIdleIcon)
return gfx::kChromeIconGrey;
if (&icon_id == &vector_icons::kMediaRouterActiveIcon)
return gfx::kGoogleBlue500;
if (&icon_id == &vector_icons::kMediaRouterWarningIcon)
return gfx::kGoogleYellow700;
DCHECK_EQ(&vector_icons::kMediaRouterErrorIcon, &icon_id);
return gfx::kGoogleRed700;
}
std::string MediaRouterAction::GetId() const {
return ComponentToolbarActionsFactory::kMediaRouterActionId;
}
void MediaRouterAction::SetDelegate(ToolbarActionViewDelegate* delegate) {
delegate_ = delegate;
// In cases such as opening a new browser window, SetDelegate() will be
// called before the WebContents is set. In those cases, we register with the
// dialog controller when ActiveTabChanged() is called.
if (delegate_ && delegate_->GetCurrentWebContents())
RegisterWithDialogController();
}
gfx::Image MediaRouterAction::GetIcon(content::WebContents* web_contents,
const gfx::Size& size) {
return gfx::Image(
gfx::CreateVectorIcon(*current_icon_, GetIconColor(*current_icon_)));
}
base::string16 MediaRouterAction::GetActionName() const {
return l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_TITLE);
}
base::string16 MediaRouterAction::GetAccessibleName(
content::WebContents* web_contents) const {
return GetTooltip(web_contents);
}
base::string16 MediaRouterAction::GetTooltip(
content::WebContents* web_contents) const {
return l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_ICON_TOOLTIP_TEXT);
}
bool MediaRouterAction::IsEnabled(
content::WebContents* web_contents) const {
return true;
}
bool MediaRouterAction::WantsToRun(
content::WebContents* web_contents) const {
return false;
}
bool MediaRouterAction::HasPopup(
content::WebContents* web_contents) const {
return true;
}
bool MediaRouterAction::IsShowingPopup() const {
auto* controller = GetMediaRouterDialogController();
return controller && controller->IsShowingMediaRouterDialog();
}
void MediaRouterAction::HidePopup() {
GetMediaRouterDialogController()->HideMediaRouterDialog();
}
gfx::NativeView MediaRouterAction::GetPopupNativeView() {
return nullptr;
}
ui::MenuModel* MediaRouterAction::GetContextMenu() {
// If there is an existing context menu, destroy it before we instantiate a
// new one.
DestroyContextMenu();
MediaRouterActionController* controller =
media_router::MediaRouterUIService::Get(browser_->profile())
->action_controller();
if (toolbar_actions_bar_->IsActionVisibleOnMainBar(this)) {
contextual_menu_ =
MediaRouterContextualMenu::CreateForToolbar(browser_, controller);
} else {
contextual_menu_ =
MediaRouterContextualMenu::CreateForOverflowMenu(browser_, controller);
}
return contextual_menu_->menu_model();
}
void MediaRouterAction::OnContextMenuClosed() {
if (toolbar_actions_bar_->popped_out_action() == this && !IsShowingPopup())
toolbar_actions_bar_->UndoPopOut();
// We must destroy the context menu asynchronously to prevent it from being
// destroyed before the command execution.
// TODO(takumif): Using task sequence to order operations is fragile. Consider
// other ways to do so when we move the icon to the trusted area.
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&MediaRouterAction::DestroyContextMenu,
weak_ptr_factory_.GetWeakPtr()));
}
bool MediaRouterAction::ExecuteAction(bool by_user) {
base::RecordAction(base::UserMetricsAction("MediaRouter_Icon_Click"));
if (IsShowingPopup()) {
HidePopup();
return false;
}
GetMediaRouterDialogController()->ShowMediaRouterDialog();
if (!skip_close_overflow_menu_for_testing_) {
// TODO(karandeepb): Instead of checking the return value of
// CloseOverflowMenuIfOpen, just check
// ToolbarActionsBar::IsActionVisibleOnMainBar.
media_router::MediaRouterMetrics::RecordMediaRouterDialogOrigin(
toolbar_actions_bar_->CloseOverflowMenuIfOpen()
? media_router::MediaRouterDialogOpenOrigin::OVERFLOW_MENU
: media_router::MediaRouterDialogOpenOrigin::TOOLBAR);
}
return true;
}
void MediaRouterAction::UpdateState() {
delegate_->UpdateState();
}
bool MediaRouterAction::DisabledClickOpensMenu() const {
return false;
}
void MediaRouterAction::OnIssue(const media_router::Issue& issue) {
current_issue_ = std::make_unique<media_router::IssueInfo>(issue.info());
MaybeUpdateIcon();
}
void MediaRouterAction::OnIssuesCleared() {
current_issue_.reset();
MaybeUpdateIcon();
}
void MediaRouterAction::OnRoutesUpdated(
const std::vector<media_router::MediaRoute>& routes,
const std::vector<media_router::MediaRoute::Id>& joinable_route_ids) {
has_local_display_route_ =
std::find_if(routes.begin(), routes.end(),
[](const media_router::MediaRoute& route) {
return route.is_local() && route.for_display();
}) != routes.end();
MaybeUpdateIcon();
}
void MediaRouterAction::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (!selection.active_tab_changed() || tab_strip_model->empty())
return;
RegisterWithDialogController();
UpdateDialogState();
}
void MediaRouterAction::OnToolbarActionsBarAnimationEnded() {
UpdateDialogState();
}
void MediaRouterAction::OnDialogHidden() {
if (has_dialog_) {
has_dialog_ = false;
delegate_->OnPopupClosed();
}
}
void MediaRouterAction::OnDialogShown() {
if (!has_dialog_) {
has_dialog_ = true;
// We depress the action regardless of whether ExecuteAction() was user
// initiated.
delegate_->OnPopupShown(true);
}
}
void MediaRouterAction::RegisterWithDialogController() {
MediaRouterDialogControllerImplBase* controller =
GetMediaRouterDialogController();
if (!controller)
return;
// |controller| keeps track of |this| if |this| was created with the browser
// window or ephemerally by activating the Cast functionality. If |this| was
// created in overflow mode, it will be destroyed when the overflow menu is
// closed.
if (!toolbar_actions_bar_->in_overflow_mode())
controller->SetMediaRouterAction(weak_ptr_factory_.GetWeakPtr());
}
void MediaRouterAction::UpdateDialogState() {
// The WebContents may be null during browser test shutdown, in which case we
// cannot call GetMediaRouterDialogController().
if (!delegate_->GetCurrentWebContents())
return;
if (IsShowingPopup())
OnDialogShown();
else
OnDialogHidden();
}
MediaRouterDialogControllerImplBase*
MediaRouterAction::GetMediaRouterDialogController() {
DCHECK(delegate_);
content::WebContents* web_contents = delegate_->GetCurrentWebContents();
DCHECK(web_contents);
return MediaRouterDialogControllerImplBase::GetOrCreateForWebContents(
web_contents);
}
const MediaRouterDialogControllerImplBase*
MediaRouterAction::GetMediaRouterDialogController() const {
DCHECK(delegate_);
content::WebContents* web_contents = delegate_->GetCurrentWebContents();
DCHECK(web_contents);
return MediaRouterDialogControllerImplBase::FromWebContents(web_contents);
}
void MediaRouterAction::MaybeUpdateIcon() {
const gfx::VectorIcon& new_icon = GetCurrentIcon();
// Update the current state if it has changed.
if (&new_icon != current_icon_) {
current_icon_ = &new_icon;
// Tell the associated view to update its icon to reflect the change made
// above. If MaybeUpdateIcon() was called as a result of instantiating
// |this|, then |delegate_| may not be set yet.
if (delegate_)
UpdateState();
}
}
const gfx::VectorIcon& MediaRouterAction::GetCurrentIcon() const {
// Highest priority is to indicate whether there's an issue.
if (current_issue_) {
media_router::IssueInfo::Severity severity = current_issue_->severity;
if (severity == media_router::IssueInfo::Severity::FATAL)
return vector_icons::kMediaRouterErrorIcon;
if (severity == media_router::IssueInfo::Severity::WARNING)
return vector_icons::kMediaRouterWarningIcon;
// Fall through for Severity::NOTIFICATION.
}
return has_local_display_route_ ? vector_icons::kMediaRouterActiveIcon
: vector_icons::kMediaRouterIdleIcon;
}
void MediaRouterAction::DestroyContextMenu() {
contextual_menu_.reset();
}