blob: 2ec54895752546066921797a6ec76ac5a3b90cf4 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/media_message_center/media_notification_view_impl.h"
#include "base/containers/contains.h"
#include "base/metrics/histogram_macros.h"
#include "components/media_message_center/media_notification_background_ash_impl.h"
#include "components/media_message_center/media_notification_background_impl.h"
#include "components/media_message_center/media_notification_container.h"
#include "components/media_message_center/media_notification_item.h"
#include "components/media_message_center/media_notification_util.h"
#include "components/media_message_center/notification_theme.h"
#include "components/media_message_center/vector_icons/vector_icons.h"
#include "components/strings/grit/components_strings.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/views/notification_header_view.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/style/typography.h"
#include "ui/views/view_class_properties.h"
namespace media_message_center {
using media_session::mojom::MediaSessionAction;
namespace {
// The number of actions supported when the notification is expanded or not.
constexpr size_t kMediaNotificationActionsCount = 3;
constexpr size_t kMediaNotificationExpandedActionsCount = 6;
// Dimensions.
constexpr int kDefaultMarginSize = 8;
constexpr int kMediaButtonIconSize = 16;
constexpr int kTitleArtistLineHeight = 20;
constexpr double kMediaImageMaxWidthPct = 0.3;
constexpr double kMediaImageMaxWidthExpandedPct = 0.4;
constexpr gfx::Size kMediaButtonSize = gfx::Size(36, 36);
constexpr int kMediaButtonRowSeparator = 0;
constexpr auto kMediaTitleArtistInsets = gfx::Insets::TLBR(8, 8, 0, 8);
constexpr auto kIconlessMediaNotificationHeaderInsets =
gfx::Insets::TLBR(6, 14, 0, 6);
constexpr auto kIconMediaNotificationHeaderInsets =
gfx::Insets::TLBR(6, 0, 0, 6);
constexpr gfx::Size kMediaNotificationButtonRowSize =
gfx::Size(124, kMediaButtonSize.height());
constexpr gfx::Size kPipButtonSeparatorViewSize = gfx::Size(20, 24);
// Dimensions for CrOS.
constexpr int kCrOSTitleLineHeight = 20;
constexpr int kCrOSArtistLineHeight = 16;
constexpr int kCrOSMediaButtonRowSeparator = 8;
constexpr int kCrOSHeaderRowSeparator = 16;
constexpr gfx::Size kCrOSMediaButtonSize = gfx::Size(32, 32);
constexpr gfx::Insets kCrOSMediaTitleArtistInsets =
gfx::Insets::TLBR(0, 8, 12, 0);
constexpr gfx::Size kCrOSMediaNotificationButtonRowSize =
gfx::Size(124, kCrOSMediaButtonSize.height());
constexpr gfx::Size kCrOSPipButtonSeparatorViewSize = gfx::Size(1, 20);
constexpr auto kCrOSHeaderRowInsets = gfx::Insets::TLBR(16, 16, 0, 16);
constexpr auto kCrOSMainRowInsetsWithArtwork =
gfx::Insets::TLBR(12, 8, 16, 111);
constexpr auto kCrOSMainRowInsetsWithoutArtwork =
gfx::Insets::TLBR(12, 8, 16, 16);
void RecordMetadataHistogram(MediaNotificationViewImpl::Metadata metadata) {
UMA_HISTOGRAM_ENUMERATION(MediaNotificationViewImpl::kMetadataHistogramName,
metadata);
}
const gfx::VectorIcon* GetVectorIconForMediaAction(MediaSessionAction action) {
switch (action) {
case MediaSessionAction::kPreviousTrack:
return &kMediaPreviousTrackIcon;
case MediaSessionAction::kSeekBackward:
return &kMediaSeekBackwardIcon;
case MediaSessionAction::kPlay:
return &kPlayArrowIcon;
case MediaSessionAction::kPause:
return &kPauseIcon;
case MediaSessionAction::kSeekForward:
return &kMediaSeekForwardIcon;
case MediaSessionAction::kNextTrack:
return &kMediaNextTrackIcon;
case MediaSessionAction::kEnterPictureInPicture:
return &kMediaEnterPipIcon;
case MediaSessionAction::kExitPictureInPicture:
return &kMediaExitPipIcon;
case MediaSessionAction::kStop:
case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
case MediaSessionAction::kSwitchAudioDevice:
case MediaSessionAction::kToggleMicrophone:
case MediaSessionAction::kToggleCamera:
case MediaSessionAction::kHangUp:
case MediaSessionAction::kRaise:
case MediaSessionAction::kSetMute:
NOTREACHED();
break;
}
return nullptr;
}
size_t GetMaxNumActions(bool expanded) {
return expanded ? kMediaNotificationExpandedActionsCount
: kMediaNotificationActionsCount;
}
} // namespace
// static
const char MediaNotificationViewImpl::kArtworkHistogramName[] =
"Media.Notification.ArtworkPresent";
// static
const char MediaNotificationViewImpl::kMetadataHistogramName[] =
"Media.Notification.MetadataPresent";
MediaNotificationViewImpl::MediaNotificationViewImpl(
MediaNotificationContainer* container,
base::WeakPtr<MediaNotificationItem> item,
std::unique_ptr<views::View> header_row_controls_view,
const std::u16string& default_app_name,
int notification_width,
bool should_show_icon,
absl::optional<NotificationTheme> theme)
: container_(container),
item_(std::move(item)),
default_app_name_(default_app_name),
notification_width_(notification_width),
theme_(theme),
is_cros_(theme.has_value()) {
DCHECK(container_);
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0));
if (is_cros_)
CreateCrOSHeaderRow(std::move(header_row_controls_view));
else
CreateHeaderRow(std::move(header_row_controls_view), should_show_icon);
// |main_row_| holds the main content of the notification.
auto main_row = std::make_unique<views::View>();
main_row_ = AddChildView(std::move(main_row));
// |title_artist_row_| contains the title and artist labels.
auto title_artist_row = std::make_unique<views::View>();
title_artist_row_layout_ =
title_artist_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
is_cros_ ? kCrOSMediaTitleArtistInsets : kMediaTitleArtistInsets, 0));
title_artist_row_layout_->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
title_artist_row_layout_->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
title_artist_row_ = main_row_->AddChildView(std::move(title_artist_row));
auto title_label = std::make_unique<views::Label>(
std::u16string(), 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::MEDIUM));
title_label->SetLineHeight(is_cros_ ? kCrOSTitleLineHeight
: kTitleArtistLineHeight);
title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title_label_ = title_artist_row_->AddChildView(std::move(title_label));
auto artist_label = std::make_unique<views::Label>(
std::u16string(), views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY);
artist_label->SetLineHeight(is_cros_ ? kCrOSArtistLineHeight
: kTitleArtistLineHeight);
artist_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
artist_label_ = title_artist_row_->AddChildView(std::move(artist_label));
// |button_row_| contains the buttons for controlling playback.
auto button_row = std::make_unique<views::View>();
auto* button_row_layout =
button_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
is_cros_ ? kCrOSMediaButtonRowSeparator : kMediaButtonRowSeparator));
button_row_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
button_row->SetPreferredSize(is_cros_ ? kCrOSMediaNotificationButtonRowSize
: kMediaNotificationButtonRowSize);
button_row_ = main_row_->AddChildView(std::move(button_row));
auto playback_button_container = std::make_unique<views::View>();
auto* playback_button_container_layout =
playback_button_container->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
is_cros_ ? kCrOSMediaButtonRowSeparator
: kMediaButtonRowSeparator));
playback_button_container_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
// Media playback controls should always be presented left-to-right,
// regardless of the local UI direction.
playback_button_container->SetMirrored(false);
playback_button_container_ =
button_row_->AddChildView(std::move(playback_button_container));
CreateMediaButton(
MediaSessionAction::kPreviousTrack,
l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PREVIOUS_TRACK));
CreateMediaButton(
MediaSessionAction::kSeekBackward,
l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_BACKWARD));
// |play_pause_button_| toggles playback.
auto play_pause_button =
views::CreateVectorToggleImageButton(views::Button::PressedCallback());
play_pause_button->SetCallback(
base::BindRepeating(&MediaNotificationViewImpl::ButtonPressed,
base::Unretained(this), play_pause_button.get()));
play_pause_button->set_tag(static_cast<int>(MediaSessionAction::kPlay));
play_pause_button->SetPreferredSize(is_cros_ ? kCrOSMediaButtonSize
: kMediaButtonSize);
play_pause_button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
play_pause_button->SetTooltipText(l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY));
play_pause_button->SetToggledTooltipText(l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PAUSE));
play_pause_button->SetFlipCanvasOnPaintForRTLUI(false);
play_pause_button_ =
playback_button_container_->AddChildView(std::move(play_pause_button));
CreateMediaButton(
MediaSessionAction::kSeekForward,
l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_FORWARD));
CreateMediaButton(
MediaSessionAction::kNextTrack,
l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_NEXT_TRACK));
auto pip_button_separator_view = std::make_unique<views::View>();
auto* pip_button_separator_view_layout =
pip_button_separator_view->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0));
pip_button_separator_view_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
pip_button_separator_view_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
pip_button_separator_view->SetPreferredSize(
is_cros_ ? kCrOSPipButtonSeparatorViewSize : kPipButtonSeparatorViewSize);
auto pip_button_separator_stroke = std::make_unique<views::View>();
pip_button_separator_stroke->SetPreferredSize(
gfx::Size(1, is_cros_ ? kCrOSPipButtonSeparatorViewSize.height()
: kPipButtonSeparatorViewSize.height()));
pip_button_separator_view->AddChildView(
std::move(pip_button_separator_stroke));
pip_button_separator_view_ =
button_row_->AddChildView(std::move(pip_button_separator_view));
auto picture_in_picture_button =
views::CreateVectorToggleImageButton(views::Button::PressedCallback());
picture_in_picture_button->SetCallback(base::BindRepeating(
&MediaNotificationViewImpl::ButtonPressed, base::Unretained(this),
picture_in_picture_button.get()));
picture_in_picture_button->set_tag(
static_cast<int>(MediaSessionAction::kEnterPictureInPicture));
picture_in_picture_button->SetPreferredSize(is_cros_ ? kCrOSMediaButtonSize
: kMediaButtonSize);
picture_in_picture_button->SetFocusBehavior(
views::View::FocusBehavior::ALWAYS);
picture_in_picture_button->SetTooltipText(l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_ENTER_PIP));
picture_in_picture_button->SetToggledTooltipText(l10n_util::GetStringUTF16(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_EXIT_PIP));
picture_in_picture_button->SetFlipCanvasOnPaintForRTLUI(false);
picture_in_picture_button_ =
button_row_->AddChildView(std::move(picture_in_picture_button));
// Use ash style background if we do have a theme.
if (is_cros_) {
SetBackground(std::make_unique<MediaNotificationBackgroundAshImpl>());
for (views::View* button : GetButtons())
views::InstallCircleHighlightPathGenerator(button);
} else {
SetBackground(std::make_unique<MediaNotificationBackgroundImpl>(
message_center::kNotificationCornerRadius,
message_center::kNotificationCornerRadius, kMediaImageMaxWidthPct));
}
UpdateCornerRadius(message_center::kNotificationCornerRadius,
message_center::kNotificationCornerRadius);
UpdateViewForExpandedState();
if (header_row_)
header_row_->SetExpandButtonEnabled(GetExpandable());
if (item_)
item_->SetView(this);
}
MediaNotificationViewImpl::~MediaNotificationViewImpl() {
if (item_)
item_->SetView(nullptr);
}
void MediaNotificationViewImpl::SetExpanded(bool expanded) {
if (expanded_ == expanded)
return;
expanded_ = expanded;
UpdateViewForExpandedState();
PreferredSizeChanged();
Layout();
SchedulePaint();
}
void MediaNotificationViewImpl::UpdateCornerRadius(int top_radius,
int bottom_radius) {
if (GetMediaNotificationBackground()->UpdateCornerRadius(top_radius,
bottom_radius)) {
SchedulePaint();
}
}
void MediaNotificationViewImpl::SetForcedExpandedState(
bool* forced_expanded_state) {
if (forced_expanded_state) {
if (forced_expanded_state_ == *forced_expanded_state)
return;
forced_expanded_state_ = *forced_expanded_state;
} else {
if (!forced_expanded_state_.has_value())
return;
forced_expanded_state_ = absl::nullopt;
}
if (header_row_)
header_row_->SetExpandButtonEnabled(GetExpandable());
UpdateViewForExpandedState();
}
void MediaNotificationViewImpl::GetAccessibleNodeData(
ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kListItem;
node_data->AddStringAttribute(
ax::mojom::StringAttribute::kRoleDescription,
l10n_util::GetStringUTF8(
IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACCESSIBLE_NAME));
if (!accessible_name_.empty())
node_data->SetNameChecked(accessible_name_);
}
void MediaNotificationViewImpl::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));
bool in_picture_in_picture =
session_info &&
session_info->picture_in_picture_state ==
media_session::mojom::MediaPictureInPictureState::kInPictureInPicture;
picture_in_picture_button_->SetToggled(in_picture_in_picture);
action = in_picture_in_picture ? MediaSessionAction::kExitPictureInPicture
: MediaSessionAction::kEnterPictureInPicture;
picture_in_picture_button_->set_tag(static_cast<int>(action));
UpdateActionButtonsVisibility();
container_->OnMediaSessionInfoChanged(session_info);
PreferredSizeChanged();
Layout();
SchedulePaint();
}
void MediaNotificationViewImpl::UpdateWithMediaMetadata(
const media_session::MediaMetadata& metadata) {
auto& app_name =
metadata.source_title.empty() ? default_app_name_ : metadata.source_title;
if (header_row_) {
header_row_->SetAppName(app_name);
header_row_->SetSummaryText(metadata.album);
} else {
cros_header_label_->SetText(app_name);
}
title_label_->SetText(metadata.title);
artist_label_->SetText(metadata.artist);
accessible_name_ = GetAccessibleNameFromMetadata(metadata);
// The title label should only be a11y-focusable when there is text to be
// read.
if (metadata.title.empty()) {
title_label_->SetFocusBehavior(FocusBehavior::NEVER);
} else {
title_label_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
RecordMetadataHistogram(Metadata::kTitle);
}
// The artist label should only be a11y-focusable when there is text to be
// read.
if (metadata.artist.empty()) {
artist_label_->SetFocusBehavior(FocusBehavior::NEVER);
} else {
artist_label_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
RecordMetadataHistogram(Metadata::kArtist);
}
if (!metadata.album.empty())
RecordMetadataHistogram(Metadata::kAlbum);
RecordMetadataHistogram(Metadata::kCount);
container_->OnMediaSessionMetadataChanged(metadata);
MaybeShowOrHideArtistLabel();
PreferredSizeChanged();
Layout();
SchedulePaint();
}
void MediaNotificationViewImpl::UpdateWithMediaActions(
const base::flat_set<media_session::mojom::MediaSessionAction>& actions) {
enabled_actions_ = actions;
if (header_row_)
header_row_->SetExpandButtonEnabled(GetExpandable());
UpdateViewForExpandedState();
PreferredSizeChanged();
Layout();
SchedulePaint();
}
void MediaNotificationViewImpl::UpdateWithMediaArtwork(
const gfx::ImageSkia& image) {
GetMediaNotificationBackground()->UpdateArtwork(image);
has_artwork_ = !image.isNull();
UpdateViewForExpandedState();
UMA_HISTOGRAM_BOOLEAN(kArtworkHistogramName, has_artwork_);
if (GetWidget())
UpdateForegroundColor();
container_->OnMediaArtworkChanged(image);
MaybeShowOrHideArtistLabel();
PreferredSizeChanged();
Layout();
SchedulePaint();
}
void MediaNotificationViewImpl::UpdateWithFavicon(const gfx::ImageSkia& icon) {
GetMediaNotificationBackground()->UpdateFavicon(icon);
if (GetWidget())
UpdateForegroundColor();
SchedulePaint();
}
void MediaNotificationViewImpl::UpdateWithVectorIcon(
const gfx::VectorIcon& vector_icon) {
if (!header_row_)
return;
vector_header_icon_ = &vector_icon;
header_row_->SetAppIconVisible(true);
header_row_->SetProperty(views::kMarginsKey,
kIconMediaNotificationHeaderInsets);
if (GetWidget())
UpdateForegroundColor();
}
void MediaNotificationViewImpl::UpdateDeviceSelectorAvailability(
bool availability) {
GetMediaNotificationBackground()->UpdateDeviceSelectorAvailability(
availability);
}
void MediaNotificationViewImpl::OnThemeChanged() {
MediaNotificationView::OnThemeChanged();
UpdateForegroundColor();
}
views::Button* MediaNotificationViewImpl::GetHeaderRowForTesting() const {
return header_row_;
}
std::u16string MediaNotificationViewImpl::GetSourceTitleForTesting() const {
return header_row_ ? header_row_->app_name_for_testing() // IN-TEST
: cros_header_label_->GetText();
}
void MediaNotificationViewImpl::UpdateActionButtonsVisibility() {
base::flat_set<MediaSessionAction> ignored_actions = {
GetPlayPauseIgnoredAction(GetActionFromButtonTag(*play_pause_button_)),
GetPictureInPictureIgnoredAction(
GetActionFromButtonTag(*picture_in_picture_button_))};
base::flat_set<MediaSessionAction> visible_actions =
GetTopVisibleActions(enabled_actions_, ignored_actions,
GetMaxNumActions(GetActuallyExpanded()));
for (auto* view : GetButtons()) {
views::Button* action_button = views::Button::AsButton(view);
bool should_show =
base::Contains(visible_actions, GetActionFromButtonTag(*action_button));
bool should_invalidate = should_show != action_button->GetVisible();
action_button->SetVisible(should_show);
if (should_invalidate)
action_button->InvalidateLayout();
if (action_button == picture_in_picture_button_) {
pip_button_separator_view_->SetVisible(should_show);
if (should_invalidate)
pip_button_separator_view_->InvalidateLayout();
}
}
// We want to give the container a list of all possibly visible actions, and
// not just currently visible actions so it can make a decision on whether or
// not to force an expanded state.
container_->OnVisibleActionsChanged(GetTopVisibleActions(
enabled_actions_, ignored_actions, GetMaxNumActions(true)));
}
void MediaNotificationViewImpl::UpdateViewForExpandedState() {
bool expanded = GetActuallyExpanded();
// 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 (is_cros_) {
static_cast<views::BoxLayout*>(button_row_->GetLayoutManager())
->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
main_row_
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
has_artwork_ ? kCrOSMainRowInsetsWithArtwork
: kCrOSMainRowInsetsWithoutArtwork,
0))
->SetDefaultFlex(1);
} else if (expanded) {
static_cast<views::BoxLayout*>(button_row_->GetLayoutManager())
->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
main_row_
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets::TLBR(
kDefaultMarginSize, kDefaultMarginSize, kDefaultMarginSize,
has_artwork_
? (notification_width_ * kMediaImageMaxWidthExpandedPct)
: kDefaultMarginSize),
kDefaultMarginSize))
->SetDefaultFlex(1);
} else {
static_cast<views::BoxLayout*>(button_row_->GetLayoutManager())
->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
main_row_
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets::TLBR(
0, kDefaultMarginSize, 14,
has_artwork_ ? (notification_width_ * kMediaImageMaxWidthPct)
: kDefaultMarginSize),
kDefaultMarginSize, true))
->SetFlexForView(title_artist_row_, 1);
}
main_row_->Layout();
if (GetMediaNotificationBackground()->UpdateArtworkMaxWidthPct(
expanded ? kMediaImageMaxWidthExpandedPct : kMediaImageMaxWidthPct)) {
SchedulePaint();
}
if (header_row_)
header_row_->SetExpanded(expanded);
container_->OnExpanded(expanded);
UpdateActionButtonsVisibility();
}
void MediaNotificationViewImpl::CreateMediaButton(
MediaSessionAction action,
const std::u16string& accessible_name) {
auto button =
views::CreateVectorImageButton(views::Button::PressedCallback());
button->SetCallback(
base::BindRepeating(&MediaNotificationViewImpl::ButtonPressed,
base::Unretained(this), button.get()));
button->set_tag(static_cast<int>(action));
button->SetPreferredSize(is_cros_ ? kCrOSMediaButtonSize : kMediaButtonSize);
button->SetAccessibleName(accessible_name);
button->SetTooltipText(accessible_name);
button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
button->SetFlipCanvasOnPaintForRTLUI(false);
playback_button_container_->AddChildView(std::move(button));
}
void MediaNotificationViewImpl::CreateHeaderRow(
std::unique_ptr<views::View> header_row_controls_view,
bool should_show_icon) {
auto header_row = std::make_unique<message_center::NotificationHeaderView>(
base::BindRepeating(
[](MediaNotificationViewImpl* view) {
view->SetExpanded(!view->expanded_);
view->container_->OnHeaderClicked();
},
base::Unretained(this)));
if (header_row_controls_view) {
header_row_controls_view_ =
header_row->AddChildView(std::move(header_row_controls_view));
}
header_row->SetAppName(default_app_name_);
header_row->SetFocusBehavior(FocusBehavior::NEVER);
if (should_show_icon) {
header_row->ClearAppIcon();
header_row->SetProperty(views::kMarginsKey,
kIconMediaNotificationHeaderInsets);
} else {
header_row->SetAppIconVisible(false);
header_row->SetProperty(views::kMarginsKey,
kIconlessMediaNotificationHeaderInsets);
}
header_row_ = AddChildView(std::move(header_row));
}
void MediaNotificationViewImpl::CreateCrOSHeaderRow(
std::unique_ptr<views::View> header_row_controls_view) {
auto cros_header_row = std::make_unique<views::View>();
auto* header_row_layout =
cros_header_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, kCrOSHeaderRowInsets,
kCrOSHeaderRowSeparator));
auto header_label = std::make_unique<views::Label>(
default_app_name_, views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY);
const gfx::FontList& base_font_list = views::Label::GetDefaultFontList();
header_label->SetFontList(base_font_list.Derive(
0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::MEDIUM));
header_label->SetLineHeight(kCrOSTitleLineHeight);
header_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
header_label->SetAutoColorReadabilityEnabled(false);
cros_header_label_ = cros_header_row->AddChildView(std::move(header_label));
header_row_layout->SetFlexForView(cros_header_label_, 1);
if (header_row_controls_view) {
header_row_controls_view_ =
cros_header_row->AddChildView(std::move(header_row_controls_view));
}
AddChildView(std::move(cros_header_row));
}
MediaNotificationBackground*
MediaNotificationViewImpl::GetMediaNotificationBackground() {
return static_cast<MediaNotificationBackground*>(background());
}
bool MediaNotificationViewImpl::GetExpandable() const {
if (forced_expanded_state_.has_value())
return false;
base::flat_set<MediaSessionAction> ignored_actions = {
GetPlayPauseIgnoredAction(GetActionFromButtonTag(*play_pause_button_)),
GetPictureInPictureIgnoredAction(
GetActionFromButtonTag(*picture_in_picture_button_))};
// If we can show more notifications if we were expanded then we should be
// expandable.
return GetTopVisibleActions(enabled_actions_, ignored_actions,
GetMaxNumActions(true))
.size() > kMediaNotificationActionsCount;
}
bool MediaNotificationViewImpl::GetActuallyExpanded() const {
if (forced_expanded_state_.has_value())
return forced_expanded_state_.value();
return expanded_ && GetExpandable();
}
void MediaNotificationViewImpl::UpdateForegroundColor() {
const SkColor background =
GetMediaNotificationBackground()->GetBackgroundColor(*this);
const SkColor foreground =
GetMediaNotificationBackground()->GetForegroundColor(*this);
NotificationTheme theme;
if (theme_.has_value()) {
theme = *theme_;
} else {
theme.primary_text_color = foreground;
theme.secondary_text_color = foreground;
theme.enabled_icon_color = foreground;
theme.disabled_icon_color =
SkColorSetA(foreground, gfx::kDisabledControlAlpha);
theme.separator_color = SkColorSetA(foreground, 0x1F);
}
title_label_->SetEnabledColor(theme.primary_text_color);
artist_label_->SetEnabledColor(theme.secondary_text_color);
if (header_row_) {
header_row_->SetColor(theme.primary_text_color);
header_row_->SetBackgroundColor(background);
} else {
cros_header_label_->SetEnabledColor(theme.primary_text_color);
}
if (vector_header_icon_ && header_row_) {
header_row_->SetAppIcon(gfx::CreateVectorIcon(
*vector_header_icon_, message_center::kSmallImageSizeMD,
theme.enabled_icon_color));
}
title_label_->SetBackgroundColor(background);
artist_label_->SetBackgroundColor(background);
pip_button_separator_view_->children().front()->SetBackground(
views::CreateSolidBackground(theme.separator_color));
// Update play/pause button images.
views::SetImageFromVectorIconWithColor(
play_pause_button_,
*GetVectorIconForMediaAction(MediaSessionAction::kPlay),
kMediaButtonIconSize, theme.enabled_icon_color,
theme.disabled_icon_color);
views::SetToggledImageFromVectorIconWithColor(
play_pause_button_,
*GetVectorIconForMediaAction(MediaSessionAction::kPause),
kMediaButtonIconSize, theme.enabled_icon_color,
theme.disabled_icon_color);
views::SetImageFromVectorIconWithColor(
picture_in_picture_button_,
*GetVectorIconForMediaAction(MediaSessionAction::kEnterPictureInPicture),
kMediaButtonIconSize, theme.enabled_icon_color,
theme.disabled_icon_color);
views::SetToggledImageFromVectorIconWithColor(
picture_in_picture_button_,
*GetVectorIconForMediaAction(MediaSessionAction::kExitPictureInPicture),
kMediaButtonIconSize, theme.enabled_icon_color,
theme.disabled_icon_color);
// Update action buttons.
for (views::View* child : playback_button_container_->children()) {
// Skip the play pause button since it is a special case.
if (child == play_pause_button_)
continue;
views::ImageButton* button = static_cast<views::ImageButton*>(child);
views::SetImageFromVectorIconWithColor(
button, *GetVectorIconForMediaAction(GetActionFromButtonTag(*button)),
kMediaButtonIconSize, theme.enabled_icon_color,
theme.disabled_icon_color);
button->SchedulePaint();
}
container_->OnColorsChanged(theme.enabled_icon_color,
theme.disabled_icon_color, background);
}
void MediaNotificationViewImpl::ButtonPressed(views::Button* button) {
if (item_)
item_->OnMediaSessionActionButtonPressed(GetActionFromButtonTag(*button));
}
void MediaNotificationViewImpl::MaybeShowOrHideArtistLabel() {
if (!is_cros_)
return;
artist_label_->SetVisible(!artist_label_->GetText().empty() || has_artwork_);
}
std::vector<views::View*> MediaNotificationViewImpl::GetButtons() {
auto buttons = button_row_->children();
buttons.insert(buttons.cbegin(),
playback_button_container_->children().cbegin(),
playback_button_container_->children().cend());
buttons.erase(
std::remove_if(buttons.begin(), buttons.end(),
[](views::View* view) {
return !(view->GetClassName() ==
views::ImageButton::kViewClassName ||
view->GetClassName() ==
views::ToggleImageButton::kViewClassName);
}),
buttons.end());
return buttons;
}
BEGIN_METADATA(MediaNotificationViewImpl, views::View)
ADD_READONLY_PROPERTY_METADATA(bool, Expandable)
ADD_READONLY_PROPERTY_METADATA(bool, ActuallyExpanded)
END_METADATA
} // namespace media_message_center