blob: 93a1a3b7a22a2ca60e17a85c9fcd8b313d387d17 [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 "ash/media/media_notification_view.h"
#include "ash/media/media_notification_constants.h"
#include "ash/media/media_notification_controller.h"
#include "ash/shell.h"
#include "base/stl_util.h"
#include "components/vector_icons/vector_icons.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/views/notification_control_buttons_view.h"
#include "ui/message_center/views/notification_header_view.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/style/typography.h"
namespace ash {
using media_session::mojom::MediaSessionAction;
namespace {
// The right padding is 1/5th the size of the notification.
constexpr int kRightMarginSize = message_center::kNotificationWidth / 5;
// The right padding is 1/3rd the size of the notification when the
// notification is expanded.
constexpr int kRightMarginExpandedSize = message_center::kNotificationWidth / 3;
// Dimensions.
constexpr int kDefaultMarginSize = 16;
constexpr int kMediaButtonIconSize = 24;
SkColor GetMediaNotificationColor(const views::View& view) {
return views::style::GetColor(view, views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY);
}
bool ShouldShowActionWhenCollapsed(MediaSessionAction action) {
return action == MediaSessionAction::kPlay ||
action == MediaSessionAction::kPause ||
action == MediaSessionAction::kNextTrack ||
action == MediaSessionAction::kPreviousTrack;
}
} // namespace
MediaNotificationView::MediaNotificationView(
const message_center::Notification& notification)
: message_center::MessageView(notification) {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical, gfx::Insets(), 0));
// |controls_button_view_| has the common notification control buttons.
control_buttons_view_ =
std::make_unique<message_center::NotificationControlButtonsView>(this);
control_buttons_view_->set_owned_by_client();
// |header_row_| contains app_icon, app_name, control buttons, etc.
header_row_ = new message_center::NotificationHeaderView(
control_buttons_view_.get(), this);
header_row_->SetExpandButtonEnabled(true);
header_row_->SetAppName(
message_center::MessageCenter::Get()->GetSystemNotificationAppName());
AddChildView(header_row_);
// |main_row_| holds the main content of the notification.
main_row_ = new views::View();
AddChildView(main_row_);
// |title_artist_row_| contains the title and artist labels.
title_artist_row_ = new views::View();
auto* title_artist_row_layout =
title_artist_row_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical, gfx::Insets(), 0));
title_artist_row_layout->set_main_axis_alignment(
views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
title_artist_row_layout->set_cross_axis_alignment(
views::BoxLayout::CROSS_AXIS_ALIGNMENT_START);
title_artist_row_->SetVisible(false);
main_row_->AddChildView(title_artist_row_);
title_label_ = new views::Label(base::string16(), views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY);
const gfx::FontList& base_font_list = views::Label::GetDefaultFontList();
title_label_->SetFontList(base_font_list.Derive(
0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::SEMIBOLD));
title_label_->SetVisible(false);
title_artist_row_->AddChildView(title_label_);
artist_label_ =
new views::Label(base::string16(), views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY);
artist_label_->SetVisible(false);
title_artist_row_->AddChildView(artist_label_);
// |button_row_| contains the buttons for controlling playback.
button_row_ = new views::View();
auto* button_row_layout =
button_row_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(), 0));
button_row_layout->set_main_axis_alignment(
views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
button_row_layout->set_cross_axis_alignment(
views::BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH);
button_row_layout->SetDefaultFlex(1);
main_row_->AddChildView(button_row_);
CreateMediaButton(vector_icons::kMediaPreviousTrackIcon,
MediaSessionAction::kPreviousTrack);
CreateMediaButton(vector_icons::kMediaSeekBackwardIcon,
MediaSessionAction::kSeekBackward);
// |play_pause_button_| toggles playback.
play_pause_button_ = views::CreateVectorToggleImageButton(this);
play_pause_button_->set_tag(static_cast<int>(MediaSessionAction::kPlay));
SkColor play_button_color = GetMediaNotificationColor(*play_pause_button_);
views::SetImageFromVectorIcon(play_pause_button_,
vector_icons::kPlayArrowIcon,
kMediaButtonIconSize, play_button_color);
views::SetToggledImageFromVectorIcon(play_pause_button_,
vector_icons::kPauseIcon,
kMediaButtonIconSize, play_button_color);
button_row_->AddChildView(play_pause_button_);
CreateMediaButton(vector_icons::kMediaSeekForwardIcon,
MediaSessionAction::kSeekForward);
CreateMediaButton(vector_icons::kMediaNextTrackIcon,
MediaSessionAction::kNextTrack);
// TODO(beccahughes): Add remaining UI for notification.
UpdateControlButtonsVisibilityWithNotification(notification);
UpdateCornerRadius(message_center::kNotificationCornerRadius,
message_center::kNotificationCornerRadius);
UpdateViewForExpandedState();
Shell::Get()->media_notification_controller()->SetView(notification_id(),
this);
}
MediaNotificationView::~MediaNotificationView() {
Shell::Get()->media_notification_controller()->SetView(notification_id(),
nullptr);
}
void MediaNotificationView::UpdateWithNotification(
const message_center::Notification& notification) {
MessageView::UpdateWithNotification(notification);
UpdateControlButtonsVisibilityWithNotification(notification);
Layout();
SchedulePaint();
}
message_center::NotificationControlButtonsView*
MediaNotificationView::GetControlButtonsView() const {
return control_buttons_view_.get();
}
void MediaNotificationView::SetExpanded(bool expanded) {
if (expanded_ == expanded)
return;
expanded_ = expanded;
UpdateViewForExpandedState();
PreferredSizeChanged();
}
void MediaNotificationView::OnMouseEvent(ui::MouseEvent* event) {
switch (event->type()) {
case ui::ET_MOUSE_ENTERED:
case ui::ET_MOUSE_EXITED:
UpdateControlButtonsVisibility();
break;
default:
break;
}
View::OnMouseEvent(event);
}
void MediaNotificationView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender == header_row_) {
SetExpanded(!expanded_);
return;
}
if (sender->parent() == button_row_) {
message_center::MessageCenter::Get()->ClickOnNotificationButton(
notification_id(), sender->tag());
return;
}
NOTREACHED();
}
void MediaNotificationView::UpdateWithMediaSessionInfo(
const media_session::mojom::MediaSessionInfoPtr& session_info) {
bool playing =
session_info && session_info->playback_state ==
media_session::mojom::MediaPlaybackState::kPlaying;
play_pause_button_->SetToggled(playing);
MediaSessionAction action =
playing ? MediaSessionAction::kPause : MediaSessionAction::kPlay;
play_pause_button_->set_tag(static_cast<int>(action));
play_pause_button_->SetVisible(IsActionButtonVisible(action));
PreferredSizeChanged();
}
void MediaNotificationView::UpdateWithMediaMetadata(
const media_session::MediaMetadata& metadata) {
if (!metadata.source_title.empty()) {
header_row_->SetAppName(metadata.source_title);
} else {
header_row_->SetAppName(
message_center::MessageCenter::Get()->GetSystemNotificationAppName());
}
if (metadata.title.empty() && metadata.artist.empty()) {
title_artist_row_->SetVisible(false);
return;
}
title_artist_row_->SetVisible(true);
title_label_->SetText(metadata.title);
title_label_->SetVisible(!metadata.title.empty());
artist_label_->SetText(metadata.artist);
artist_label_->SetVisible(!metadata.artist.empty());
}
void MediaNotificationView::UpdateWithMediaActions(
const std::set<media_session::mojom::MediaSessionAction>& actions) {
enabled_actions_ = actions;
UpdateActionButtonsVisibility();
PreferredSizeChanged();
}
void MediaNotificationView::UpdateControlButtonsVisibilityWithNotification(
const message_center::Notification& notification) {
// Media notifications do not use the settings and snooze buttons.
DCHECK(!notification.should_show_settings_button());
DCHECK(!notification.should_show_snooze_button());
control_buttons_view_->ShowCloseButton(!notification.pinned());
UpdateControlButtonsVisibility();
}
bool MediaNotificationView::IsActionButtonVisible(
MediaSessionAction action) const {
// Not all media sessions support the same actions.
bool visible = base::ContainsKey(enabled_actions_, action);
// We should reduce the number of action buttons we show when we are
// collapsed.
// TODO(beccahughes): Use priority based ranking here
if (!expanded_ && visible)
visible = ShouldShowActionWhenCollapsed(action);
return visible;
}
void MediaNotificationView::UpdateActionButtonsVisibility() {
for (int i = 0; i < button_row_->child_count(); ++i) {
views::Button* action_button =
views::Button::AsButton(button_row_->child_at(i));
action_button->SetVisible(IsActionButtonVisible(
static_cast<MediaSessionAction>(action_button->tag())));
}
}
void MediaNotificationView::UpdateViewForExpandedState() {
// Adjust the layout of the |main_row_| based on the expanded state. If the
// notification is expanded then the buttons should be below the title/artist
// information. If it is collapsed then the buttons will be to the right.
if (expanded_) {
main_row_
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical,
gfx::Insets(kDefaultMarginSize, kDefaultMarginSize,
kDefaultMarginSize, kRightMarginExpandedSize),
kDefaultMarginSize))
->SetDefaultFlex(1);
} else {
main_row_
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal,
gfx::Insets(0, kDefaultMarginSize, kDefaultMarginSize,
kRightMarginSize),
kDefaultMarginSize, true))
->SetDefaultFlex(1);
}
header_row_->SetExpanded(expanded_);
UpdateActionButtonsVisibility();
}
void MediaNotificationView::CreateMediaButton(const gfx::VectorIcon& icon,
MediaSessionAction action) {
views::ImageButton* button = views::CreateVectorImageButton(this);
button->set_tag(static_cast<int>(action));
views::SetImageFromVectorIcon(button, icon, kMediaButtonIconSize,
GetMediaNotificationColor(*button));
button_row_->AddChildView(button);
}
} // namespace ash