| // Copyright 2020 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 "components/media_message_center/media_notification_view_modern_impl.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/ranges/algorithm.h" |
| #include "components/media_message_center/media_artwork_view.h" |
| #include "components/media_message_center/media_controls_progress_view.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/media_notification_volume_slider_view.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 "components/vector_icons/vector_icons.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_header_macros.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/font.h" |
| #include "ui/gfx/font_list.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/message_center/public/cpp/message_center_constants.h" |
| #include "ui/views/animation/ink_drop.h" |
| #include "ui/views/border.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 { |
| |
| constexpr gfx::Size kMediaNotificationViewBaseSize = {350, 168}; |
| constexpr gfx::Size kArtworkSize = {72, 72}; |
| constexpr gfx::Size kInfoContainerSize = { |
| kMediaNotificationViewBaseSize.width(), kArtworkSize.height()}; |
| constexpr int kArtworkVignetteCornerRadius = 5; |
| constexpr gfx::Size kLabelsContainerBaseSize = { |
| kMediaNotificationViewBaseSize.width() - kArtworkSize.width(), |
| kInfoContainerSize.height()}; |
| constexpr gfx::Size kPipButtonSize = {30, 20}; |
| constexpr int kPipButtonIconSize = 16; |
| constexpr auto kNotificationControlsInsets = gfx::Insets::TLBR(8, 0, 0, 8); |
| constexpr gfx::Size kButtonsContainerSize = { |
| kMediaNotificationViewBaseSize.width(), 40}; |
| constexpr gfx::Size kMediaControlsContainerSize = { |
| 328 /* base width - dismissbutton size - margin*/, 32}; |
| constexpr auto kMediaControlsContainerInsets = gfx::Insets::TLBR(0, 22, 0, 0); |
| constexpr int kMediaControlsButtonSpacing = 16; |
| constexpr auto kProgressBarInsets = gfx::Insets::TLBR(0, 16, 0, 16); |
| constexpr auto kInfoContainerInsets = gfx::Insets::TLBR(0, 15, 0, 16); |
| constexpr int kInfoContainerSpacing = 12; |
| constexpr gfx::Size kUtilButtonsContainerSize = { |
| kMediaNotificationViewBaseSize.width(), 39}; |
| constexpr auto kUtilButtonsContainerInsets = gfx::Insets::TLBR(7, 16, 12, 16); |
| constexpr int kUtilButtonsSpacing = 8; |
| |
| constexpr int kTitleArtistLineHeight = 20; |
| constexpr gfx::Size kMediaButtonSize = {24, 24}; |
| constexpr gfx::Size kPlayPauseButtonSize = {32, 32}; |
| constexpr int kMediaButtonIconSize = 14; |
| constexpr int kPlayPauseIconSize = 20; |
| constexpr gfx::Size kFaviconSize = {20, 20}; |
| |
| constexpr gfx::Size kVolumeSliderSize = {50, 20}; |
| constexpr gfx::Size kMuteButtonSize = {20, 20}; |
| constexpr int kMuteButtonIconSize = 16; |
| |
| void RecordMetadataHistogram( |
| MediaNotificationViewModernImpl::Metadata metadata) { |
| UMA_HISTOGRAM_ENUMERATION( |
| MediaNotificationViewModernImpl::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; |
| } |
| |
| const std::u16string GetAccessibleNameForMediaAction( |
| MediaSessionAction action) { |
| switch (action) { |
| case MediaSessionAction::kPreviousTrack: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PREVIOUS_TRACK); |
| case MediaSessionAction::kSeekBackward: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_BACKWARD); |
| case MediaSessionAction::kPlay: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY); |
| case MediaSessionAction::kPause: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PAUSE); |
| case MediaSessionAction::kSeekForward: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_FORWARD); |
| case MediaSessionAction::kNextTrack: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_NEXT_TRACK); |
| case MediaSessionAction::kEnterPictureInPicture: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_ENTER_PIP); |
| case MediaSessionAction::kExitPictureInPicture: |
| return l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_EXIT_PIP); |
| 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 std::u16string(); |
| } |
| |
| class MediaButton : public views::ImageButton { |
| public: |
| MediaButton(PressedCallback callback, int icon_size, gfx::Size button_size) |
| : ImageButton(callback), icon_size_(icon_size) { |
| SetHasInkDropActionOnClick(true); |
| views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(), |
| button_size.height() / 2); |
| views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON); |
| views::InkDrop::Get(this)->SetBaseColorCallback(base::BindRepeating( |
| &MediaButton::GetForegroundColor, base::Unretained(this))); |
| SetImageHorizontalAlignment(ImageButton::ALIGN_CENTER); |
| SetImageVerticalAlignment(ImageButton::ALIGN_MIDDLE); |
| SetFocusBehavior(views::View::FocusBehavior::ALWAYS); |
| SetFlipCanvasOnPaintForRTLUI(false); |
| SetPreferredSize(button_size); |
| } |
| |
| void SetButtonColor(SkColor foreground_color, |
| SkColor foreground_disabled_color) { |
| foreground_color_ = foreground_color; |
| foreground_disabled_color_ = foreground_disabled_color; |
| |
| views::SetImageFromVectorIconWithColor( |
| this, *GetVectorIconForMediaAction(GetActionFromButtonTag(*this)), |
| icon_size_, foreground_color_, foreground_disabled_color_); |
| |
| SchedulePaint(); |
| } |
| |
| void set_tag(int tag) { |
| views::ImageButton::set_tag(tag); |
| |
| SetTooltipText( |
| GetAccessibleNameForMediaAction(GetActionFromButtonTag(*this))); |
| SetAccessibleName( |
| GetAccessibleNameForMediaAction(GetActionFromButtonTag(*this))); |
| views::SetImageFromVectorIconWithColor( |
| this, *GetVectorIconForMediaAction(GetActionFromButtonTag(*this)), |
| icon_size_, foreground_color_, foreground_disabled_color_); |
| } |
| |
| private: |
| SkColor GetForegroundColor() { return foreground_color_; } |
| |
| SkColor foreground_color_ = gfx::kPlaceholderColor; |
| SkColor foreground_disabled_color_ = gfx::kPlaceholderColor; |
| int icon_size_; |
| }; |
| |
| } // anonymous namespace |
| |
| // static |
| const char MediaNotificationViewModernImpl::kArtworkHistogramName[] = |
| "Media.Notification.ArtworkPresent"; |
| |
| // static |
| const char MediaNotificationViewModernImpl::kMetadataHistogramName[] = |
| "Media.Notification.MetadataPresent"; |
| |
| MediaNotificationViewModernImpl::MediaNotificationViewModernImpl( |
| MediaNotificationContainer* container, |
| base::WeakPtr<MediaNotificationItem> item, |
| std::unique_ptr<views::View> notification_controls_view, |
| std::unique_ptr<views::View> notification_footer_view, |
| int notification_width, |
| absl::optional<NotificationTheme> theme) |
| : container_(container), item_(std::move(item)), theme_(theme) { |
| DCHECK(container_); |
| |
| DCHECK(notification_controls_view); |
| |
| SetPreferredSize(kMediaNotificationViewBaseSize); |
| |
| DCHECK(notification_width >= kMediaNotificationViewBaseSize.width()) |
| << "MediaNotificationViewModernImpl expects a width of at least " |
| << kMediaNotificationViewBaseSize.width(); |
| auto border_insets = gfx::Insets::VH( |
| 0, (kMediaNotificationViewBaseSize.width() - notification_width) / 2); |
| SetBorder(views::CreateEmptyBorder(border_insets)); |
| |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0)); |
| |
| bool is_cros = theme_.has_value(); |
| if (is_cros) { |
| // We don't want the background to paint the artwork since we're painting it |
| // ourselves. |
| SetBackground(std::make_unique<MediaNotificationBackgroundAshImpl>( |
| /*paint_artwork=*/false)); |
| } else { |
| // Force the artwork width to be zero since we're painting the artwork |
| // ourselves. |
| SetBackground(std::make_unique<MediaNotificationBackgroundImpl>( |
| message_center::kNotificationCornerRadius, |
| message_center::kNotificationCornerRadius, |
| /*artwork_max_width_pct=*/0)); |
| } |
| |
| UpdateCornerRadius(message_center::kNotificationCornerRadius, |
| message_center::kNotificationCornerRadius); |
| |
| { |
| // The button container contains media_controls_container_ and |
| // notification_controls_view. |
| auto buttons_container = std::make_unique<views::View>(); |
| buttons_container->SetPreferredSize(kButtonsContainerSize); |
| auto* buttons_container_layout = |
| buttons_container->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0)); |
| buttons_container_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kStart); |
| |
| // The media controls container contains buttons for media playback. This |
| // includes play/pause, fast-forward/rewind, and skip controls. |
| auto media_controls_container = std::make_unique<views::View>(); |
| media_controls_container->SetPreferredSize(kMediaControlsContainerSize); |
| auto* media_controls_layout = media_controls_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| kMediaControlsContainerInsets, kMediaControlsButtonSpacing)); |
| media_controls_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| media_controls_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| |
| // Media controls should always be presented left-to-right, |
| // regardless of the local UI direction. |
| media_controls_container->SetMirrored(false); |
| |
| CreateMediaButton(media_controls_container.get(), |
| MediaSessionAction::kPreviousTrack); |
| CreateMediaButton(media_controls_container.get(), |
| MediaSessionAction::kSeekBackward); |
| |
| { |
| auto play_pause_button = std::make_unique<MediaButton>( |
| views::Button::PressedCallback(), kPlayPauseIconSize, |
| kPlayPauseButtonSize); |
| play_pause_button->SetCallback( |
| base::BindRepeating(&MediaNotificationViewModernImpl::ButtonPressed, |
| base::Unretained(this), play_pause_button.get())); |
| play_pause_button->set_tag(static_cast<int>(MediaSessionAction::kPlay)); |
| play_pause_button_ = |
| media_controls_container->AddChildView(std::move(play_pause_button)); |
| } |
| |
| CreateMediaButton(media_controls_container.get(), |
| MediaSessionAction::kSeekForward); |
| CreateMediaButton(media_controls_container.get(), |
| MediaSessionAction::kNextTrack); |
| |
| media_controls_container_ = |
| buttons_container->AddChildView(std::move(media_controls_container)); |
| buttons_container_layout->SetFlexForView(media_controls_container_, 1); |
| |
| notification_controls_view->SetProperty(views::kMarginsKey, |
| kNotificationControlsInsets); |
| buttons_container->AddChildView(std::move(notification_controls_view)); |
| |
| AddChildView(std::move(buttons_container)); |
| } |
| |
| auto progress_view = std::make_unique<MediaControlsProgressView>( |
| base::BindRepeating(&MediaNotificationViewModernImpl::SeekTo, |
| base::Unretained(this)), |
| true /* is_modern_notification */); |
| progress_view->SetProperty(views::kMarginsKey, kProgressBarInsets); |
| progress_ = AddChildView(std::move(progress_view)); |
| progress_->SetVisible(true); |
| |
| { |
| // The info container contains the notification artwork, the labels for the |
| // title and artist text, the picture in picture button, and the dismiss |
| // button. |
| auto info_container = std::make_unique<views::View>(); |
| info_container->SetPreferredSize(kInfoContainerSize); |
| |
| auto* info_container_layout = |
| info_container->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, kInfoContainerInsets, |
| kInfoContainerSpacing)); |
| info_container_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| |
| { |
| auto artwork_container = std::make_unique<views::View>(); |
| artwork_container->SetPreferredSize(kArtworkSize); |
| |
| auto* artwork_container_layout = artwork_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0)); |
| artwork_container_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| artwork_container_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| |
| { |
| auto artwork = std::make_unique<MediaArtworkView>( |
| kArtworkVignetteCornerRadius, kArtworkSize, kFaviconSize); |
| artwork_ = artwork_container->AddChildView(std::move(artwork)); |
| } |
| |
| artwork_container_ = |
| info_container->AddChildView(std::move(artwork_container)); |
| } |
| |
| { |
| auto labels_container = std::make_unique<views::View>(); |
| |
| labels_container->SetPreferredSize( |
| gfx::Size(kLabelsContainerBaseSize.width() - |
| kNotificationControlsInsets.width(), |
| kLabelsContainerBaseSize.height())); |
| |
| auto* labels_container_layout_manager = |
| labels_container->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0)); |
| labels_container_layout_manager->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| labels_container_layout_manager->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kStart); |
| |
| { |
| auto title_label = std::make_unique<views::Label>( |
| base::EmptyString16(), views::style::CONTEXT_LABEL, |
| views::style::STYLE_PRIMARY); |
| title_label->SetLineHeight(kTitleArtistLineHeight); |
| title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| title_label_ = labels_container->AddChildView(std::move(title_label)); |
| } |
| |
| { |
| auto subtitle_label = std::make_unique<views::Label>( |
| base::EmptyString16(), views::style::CONTEXT_LABEL, |
| views::style::STYLE_SECONDARY); |
| subtitle_label->SetLineHeight(18); |
| subtitle_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| subtitle_label_ = |
| labels_container->AddChildView(std::move(subtitle_label)); |
| } |
| |
| info_container->AddChildView(std::move(labels_container)); |
| } |
| |
| AddChildView(std::move(info_container)); |
| } |
| |
| { |
| // This view contains pip button, mute button and cast buttons. |
| auto util_buttons_container = std::make_unique<views::View>(); |
| util_buttons_container->SetPreferredSize(kUtilButtonsContainerSize); |
| auto* util_buttons_layout = util_buttons_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| kUtilButtonsContainerInsets, kUtilButtonsSpacing)); |
| util_buttons_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kStart); |
| util_buttons_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kStretch); |
| |
| if (item_->SourceType() != SourceType::kCast) { |
| // The picture-in-picture button appears directly under the media |
| // labels. |
| auto picture_in_picture_button = std::make_unique<MediaButton>( |
| views::Button::PressedCallback(), kPipButtonIconSize, kPipButtonSize); |
| picture_in_picture_button->SetCallback(base::BindRepeating( |
| &MediaNotificationViewModernImpl::ButtonPressed, |
| base::Unretained(this), picture_in_picture_button.get())); |
| picture_in_picture_button->set_tag( |
| static_cast<int>(MediaSessionAction::kEnterPictureInPicture)); |
| picture_in_picture_button_ = util_buttons_container->AddChildView( |
| std::move(picture_in_picture_button)); |
| } |
| |
| if (notification_footer_view) { |
| auto* footer_view = util_buttons_container->AddChildView( |
| std::move(notification_footer_view)); |
| util_buttons_layout->SetFlexForView(footer_view, 1); |
| } |
| |
| if (item_->SourceType() == SourceType::kCast) { |
| auto volume_slider = std::make_unique<MediaNotificationVolumeSliderView>( |
| base::BindRepeating(&MediaNotificationViewModernImpl::SetVolume, |
| base::Unretained(this))); |
| volume_slider->SetPreferredSize(kVolumeSliderSize); |
| volume_slider_ = |
| util_buttons_container->AddChildView(std::move(volume_slider)); |
| } |
| |
| auto mute_button = |
| std::make_unique<views::ToggleImageButton>(base::BindRepeating( |
| &MediaNotificationViewModernImpl::OnMuteButtonClicked, |
| base::Unretained(this))); |
| mute_button->SetPreferredSize(kMuteButtonSize); |
| mute_button->SetImageHorizontalAlignment( |
| views::ImageButton::HorizontalAlignment::ALIGN_CENTER); |
| mute_button->SetImageVerticalAlignment( |
| views::ImageButton::VerticalAlignment::ALIGN_MIDDLE); |
| mute_button->SetTooltipText(l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_MUTE)); |
| mute_button->SetAccessibleName(l10n_util::GetStringUTF16( |
| IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_MUTE)); |
| mute_button_ = util_buttons_container->AddChildView(std::move(mute_button)); |
| |
| AddChildView(std::move(util_buttons_container)); |
| } |
| |
| if (item_) |
| item_->SetView(this); |
| } |
| |
| MediaNotificationViewModernImpl::~MediaNotificationViewModernImpl() { |
| if (item_) |
| item_->SetView(nullptr); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateCornerRadius(int top_radius, |
| int bottom_radius) { |
| if (GetMediaNotificationBackground()->UpdateCornerRadius(top_radius, |
| bottom_radius)) { |
| SchedulePaint(); |
| } |
| } |
| |
| void MediaNotificationViewModernImpl::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->SetName(accessible_name_); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateWithMediaSessionInfo( |
| const media_session::mojom::MediaSessionInfoPtr& session_info) { |
| bool playing = |
| session_info && session_info->playback_state == |
| media_session::mojom::MediaPlaybackState::kPlaying; |
| |
| 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; |
| |
| if (picture_in_picture_button_) { |
| 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 MediaNotificationViewModernImpl::UpdateWithMediaMetadata( |
| const media_session::MediaMetadata& metadata) { |
| title_label_->SetText(metadata.title); |
| subtitle_label_->SetText(metadata.source_title); |
| |
| 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 subtitle label should only be a11y-focusable when there is text to be |
| // read. |
| if (metadata.source_title.empty()) { |
| subtitle_label_->SetFocusBehavior(FocusBehavior::NEVER); |
| } else { |
| subtitle_label_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); |
| RecordMetadataHistogram(Metadata::kSource); |
| } |
| |
| RecordMetadataHistogram(Metadata::kCount); |
| |
| container_->OnMediaSessionMetadataChanged(metadata); |
| |
| PreferredSizeChanged(); |
| Layout(); |
| SchedulePaint(); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateWithMediaActions( |
| const base::flat_set<media_session::mojom::MediaSessionAction>& actions) { |
| enabled_actions_ = actions; |
| |
| UpdateActionButtonsVisibility(); |
| |
| PreferredSizeChanged(); |
| Layout(); |
| SchedulePaint(); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateWithMediaPosition( |
| const media_session::MediaPosition& position) { |
| position_ = position; |
| progress_->UpdateProgress(position); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateWithMediaArtwork( |
| const gfx::ImageSkia& image) { |
| GetMediaNotificationBackground()->UpdateArtwork(image); |
| |
| UMA_HISTOGRAM_BOOLEAN(kArtworkHistogramName, !image.isNull()); |
| artwork_->SetImage(image); |
| artwork_->SetPreferredSize(kArtworkSize); |
| |
| UpdateForegroundColor(); |
| |
| container_->OnMediaArtworkChanged(image); |
| |
| PreferredSizeChanged(); |
| Layout(); |
| SchedulePaint(); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateWithFavicon( |
| const gfx::ImageSkia& icon) { |
| GetMediaNotificationBackground()->UpdateFavicon(icon); |
| |
| artwork_->SetFavicon(icon); |
| artwork_->SetPreferredSize(kArtworkSize); |
| UpdateForegroundColor(); |
| SchedulePaint(); |
| } |
| |
| void MediaNotificationViewModernImpl::OnThemeChanged() { |
| MediaNotificationView::OnThemeChanged(); |
| UpdateForegroundColor(); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateDeviceSelectorAvailability( |
| bool availability) { |
| GetMediaNotificationBackground()->UpdateDeviceSelectorAvailability( |
| availability); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateWithMuteStatus(bool mute) { |
| if (mute_button_) { |
| mute_button_->SetToggled(mute); |
| const auto mute_button_description_id = |
| mute ? IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_UNMUTE |
| : IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_MUTE; |
| mute_button_->SetTooltipText( |
| l10n_util::GetStringUTF16(mute_button_description_id)); |
| mute_button_->SetAccessibleName( |
| l10n_util::GetStringUTF16(mute_button_description_id)); |
| } |
| |
| if (volume_slider_) |
| volume_slider_->SetMute(mute); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateWithVolume(float volume) { |
| if (volume_slider_) |
| volume_slider_->SetVolume(volume); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateActionButtonsVisibility() { |
| for (auto* view : media_controls_container_->children()) { |
| views::Button* action_button = views::Button::AsButton(view); |
| bool should_show = base::Contains(enabled_actions_, |
| GetActionFromButtonTag(*action_button)); |
| bool should_invalidate = should_show != action_button->GetVisible(); |
| |
| action_button->SetVisible(should_show); |
| |
| if (should_invalidate) |
| action_button->InvalidateLayout(); |
| } |
| |
| if (picture_in_picture_button_) { |
| const bool should_show_pip = |
| base::ranges::any_of(enabled_actions_, [](MediaSessionAction action) { |
| return action == MediaSessionAction::kEnterPictureInPicture || |
| action == MediaSessionAction::kExitPictureInPicture; |
| }); |
| |
| if (picture_in_picture_button_->GetVisible() != should_show_pip) { |
| picture_in_picture_button_->SetVisible(should_show_pip); |
| picture_in_picture_button_->InvalidateLayout(); |
| } |
| } |
| |
| container_->OnVisibleActionsChanged(enabled_actions_); |
| } |
| |
| void MediaNotificationViewModernImpl::CreateMediaButton( |
| views::View* parent_view, |
| MediaSessionAction action) { |
| auto button = std::make_unique<MediaButton>( |
| views::Button::PressedCallback(), kMediaButtonIconSize, kMediaButtonSize); |
| button->SetCallback( |
| base::BindRepeating(&MediaNotificationViewModernImpl::ButtonPressed, |
| base::Unretained(this), button.get())); |
| button->set_tag(static_cast<int>(action)); |
| parent_view->AddChildView(std::move(button)); |
| } |
| |
| MediaNotificationBackground* |
| MediaNotificationViewModernImpl::GetMediaNotificationBackground() { |
| return static_cast<MediaNotificationBackground*>(background()); |
| } |
| |
| void MediaNotificationViewModernImpl::UpdateForegroundColor() { |
| if (!GetWidget()) |
| return; |
| |
| const SkColor background = |
| GetMediaNotificationBackground()->GetBackgroundColor(*this); |
| const SkColor foreground = |
| GetMediaNotificationBackground()->GetForegroundColor(*this); |
| const SkColor disabled_icon_color = |
| SkColorSetA(foreground, gfx::kDisabledControlAlpha); |
| |
| 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 = disabled_icon_color; |
| theme.separator_color = SkColorSetA(foreground, 0x1F); |
| } |
| |
| artwork_->SetBackgroundColor(theme.disabled_icon_color); |
| artwork_->SetVignetteColor(background); |
| |
| progress_->SetForegroundColor(theme.primary_text_color); |
| progress_->SetBackgroundColor(theme.disabled_icon_color); |
| progress_->SetTextColor(theme.primary_text_color); |
| |
| if (volume_slider_) |
| volume_slider_->UpdateColor(theme.primary_text_color, |
| theme.disabled_icon_color); |
| |
| if (mute_button_) { |
| views::SetImageFromVectorIconWithColor( |
| mute_button_, vector_icons::kVolumeUpIcon, kMuteButtonIconSize, |
| theme.enabled_icon_color, theme.disabled_icon_color); |
| views::SetToggledImageFromVectorIconWithColor( |
| mute_button_, vector_icons::kVolumeOffIcon, kMediaButtonIconSize, |
| theme.enabled_icon_color, theme.disabled_icon_color); |
| } |
| |
| // Update the colors for the labels |
| title_label_->SetEnabledColor(theme.primary_text_color); |
| subtitle_label_->SetEnabledColor(theme.secondary_text_color); |
| |
| title_label_->SetBackgroundColor(background); |
| subtitle_label_->SetBackgroundColor(background); |
| |
| // Update the colors for the toggle buttons (play/pause and |
| // picture-in-picture) |
| play_pause_button_->SetButtonColor(theme.enabled_icon_color, |
| theme.disabled_icon_color); |
| |
| if (picture_in_picture_button_) |
| picture_in_picture_button_->SetButtonColor(theme.enabled_icon_color, |
| theme.disabled_icon_color); |
| |
| // Update the colors for the media control buttons. |
| for (views::View* child : media_controls_container_->children()) { |
| // Skip the play pause button since it is a special case. |
| if (child == play_pause_button_) |
| continue; |
| |
| MediaButton* button = static_cast<MediaButton*>(child); |
| |
| button->SetButtonColor(theme.enabled_icon_color, theme.disabled_icon_color); |
| } |
| |
| SchedulePaint(); |
| container_->OnColorsChanged(theme.enabled_icon_color, |
| theme.disabled_icon_color, background); |
| } |
| |
| void MediaNotificationViewModernImpl::ButtonPressed(views::Button* button) { |
| if (item_) |
| item_->OnMediaSessionActionButtonPressed(GetActionFromButtonTag(*button)); |
| } |
| |
| void MediaNotificationViewModernImpl::SeekTo(double seek_progress) { |
| item_->SeekTo(seek_progress * position_.duration()); |
| } |
| |
| void MediaNotificationViewModernImpl::OnMuteButtonClicked() { |
| item_->SetMute(!mute_button_->GetToggled()); |
| } |
| |
| void MediaNotificationViewModernImpl::SetVolume(float volume) { |
| item_->SetVolume(volume); |
| item_->SetMute(volume == 0); |
| } |
| |
| views::Button* |
| MediaNotificationViewModernImpl::picture_in_picture_button_for_testing() const { |
| return picture_in_picture_button_; |
| } |
| |
| BEGIN_METADATA(MediaNotificationViewModernImpl, views::View) |
| END_METADATA |
| |
| } // namespace media_message_center |