media_notification_view_modern_impl.cc 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/media_message_center/media_notification_view_modern_impl.h"
  5. #include "base/containers/contains.h"
  6. #include "base/metrics/histogram_macros.h"
  7. #include "base/ranges/algorithm.h"
  8. #include "components/media_message_center/media_artwork_view.h"
  9. #include "components/media_message_center/media_controls_progress_view.h"
  10. #include "components/media_message_center/media_notification_background_ash_impl.h"
  11. #include "components/media_message_center/media_notification_background_impl.h"
  12. #include "components/media_message_center/media_notification_container.h"
  13. #include "components/media_message_center/media_notification_item.h"
  14. #include "components/media_message_center/media_notification_util.h"
  15. #include "components/media_message_center/media_notification_volume_slider_view.h"
  16. #include "components/media_message_center/notification_theme.h"
  17. #include "components/media_message_center/vector_icons/vector_icons.h"
  18. #include "components/strings/grit/components_strings.h"
  19. #include "components/vector_icons/vector_icons.h"
  20. #include "services/media_session/public/mojom/media_session.mojom.h"
  21. #include "ui/accessibility/ax_enums.mojom.h"
  22. #include "ui/accessibility/ax_node_data.h"
  23. #include "ui/base/l10n/l10n_util.h"
  24. #include "ui/base/metadata/metadata_header_macros.h"
  25. #include "ui/base/metadata/metadata_impl_macros.h"
  26. #include "ui/gfx/canvas.h"
  27. #include "ui/gfx/color_palette.h"
  28. #include "ui/gfx/font.h"
  29. #include "ui/gfx/font_list.h"
  30. #include "ui/gfx/geometry/skia_conversions.h"
  31. #include "ui/gfx/paint_vector_icon.h"
  32. #include "ui/message_center/public/cpp/message_center_constants.h"
  33. #include "ui/views/animation/ink_drop.h"
  34. #include "ui/views/border.h"
  35. #include "ui/views/controls/button/image_button_factory.h"
  36. #include "ui/views/controls/highlight_path_generator.h"
  37. #include "ui/views/layout/box_layout.h"
  38. #include "ui/views/style/typography.h"
  39. #include "ui/views/view_class_properties.h"
  40. namespace media_message_center {
  41. using media_session::mojom::MediaSessionAction;
  42. namespace {
  43. constexpr gfx::Size kMediaNotificationViewBaseSize = {350, 168};
  44. constexpr gfx::Size kArtworkSize = {72, 72};
  45. constexpr gfx::Size kInfoContainerSize = {
  46. kMediaNotificationViewBaseSize.width(), kArtworkSize.height()};
  47. constexpr int kArtworkVignetteCornerRadius = 5;
  48. constexpr gfx::Size kLabelsContainerBaseSize = {
  49. kMediaNotificationViewBaseSize.width() - kArtworkSize.width(),
  50. kInfoContainerSize.height()};
  51. constexpr gfx::Size kPipButtonSize = {30, 20};
  52. constexpr int kPipButtonIconSize = 16;
  53. constexpr auto kNotificationControlsInsets = gfx::Insets::TLBR(8, 0, 0, 8);
  54. constexpr gfx::Size kButtonsContainerSize = {
  55. kMediaNotificationViewBaseSize.width(), 40};
  56. constexpr gfx::Size kMediaControlsContainerSize = {
  57. 328 /* base width - dismissbutton size - margin*/, 32};
  58. constexpr auto kMediaControlsContainerInsets = gfx::Insets::TLBR(0, 22, 0, 0);
  59. constexpr int kMediaControlsButtonSpacing = 16;
  60. constexpr auto kProgressBarInsets = gfx::Insets::TLBR(0, 16, 0, 16);
  61. constexpr auto kInfoContainerInsets = gfx::Insets::TLBR(0, 15, 0, 16);
  62. constexpr int kInfoContainerSpacing = 12;
  63. constexpr gfx::Size kUtilButtonsContainerSize = {
  64. kMediaNotificationViewBaseSize.width(), 39};
  65. constexpr auto kUtilButtonsContainerInsets = gfx::Insets::TLBR(7, 16, 12, 16);
  66. constexpr int kUtilButtonsSpacing = 8;
  67. constexpr int kTitleArtistLineHeight = 20;
  68. constexpr gfx::Size kMediaButtonSize = {24, 24};
  69. constexpr gfx::Size kPlayPauseButtonSize = {32, 32};
  70. constexpr int kMediaButtonIconSize = 14;
  71. constexpr int kPlayPauseIconSize = 20;
  72. constexpr gfx::Size kFaviconSize = {20, 20};
  73. constexpr gfx::Size kVolumeSliderSize = {50, 20};
  74. constexpr gfx::Size kMuteButtonSize = {20, 20};
  75. constexpr int kMuteButtonIconSize = 16;
  76. void RecordMetadataHistogram(
  77. MediaNotificationViewModernImpl::Metadata metadata) {
  78. UMA_HISTOGRAM_ENUMERATION(
  79. MediaNotificationViewModernImpl::kMetadataHistogramName, metadata);
  80. }
  81. const gfx::VectorIcon* GetVectorIconForMediaAction(MediaSessionAction action) {
  82. switch (action) {
  83. case MediaSessionAction::kPreviousTrack:
  84. return &kMediaPreviousTrackIcon;
  85. case MediaSessionAction::kSeekBackward:
  86. return &kMediaSeekBackwardIcon;
  87. case MediaSessionAction::kPlay:
  88. return &kPlayArrowIcon;
  89. case MediaSessionAction::kPause:
  90. return &kPauseIcon;
  91. case MediaSessionAction::kSeekForward:
  92. return &kMediaSeekForwardIcon;
  93. case MediaSessionAction::kNextTrack:
  94. return &kMediaNextTrackIcon;
  95. case MediaSessionAction::kEnterPictureInPicture:
  96. return &kMediaEnterPipIcon;
  97. case MediaSessionAction::kExitPictureInPicture:
  98. return &kMediaExitPipIcon;
  99. case MediaSessionAction::kStop:
  100. case MediaSessionAction::kSkipAd:
  101. case MediaSessionAction::kSeekTo:
  102. case MediaSessionAction::kScrubTo:
  103. case MediaSessionAction::kSwitchAudioDevice:
  104. case MediaSessionAction::kToggleMicrophone:
  105. case MediaSessionAction::kToggleCamera:
  106. case MediaSessionAction::kHangUp:
  107. case MediaSessionAction::kRaise:
  108. case MediaSessionAction::kSetMute:
  109. NOTREACHED();
  110. break;
  111. }
  112. return nullptr;
  113. }
  114. const std::u16string GetAccessibleNameForMediaAction(
  115. MediaSessionAction action) {
  116. switch (action) {
  117. case MediaSessionAction::kPreviousTrack:
  118. return l10n_util::GetStringUTF16(
  119. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PREVIOUS_TRACK);
  120. case MediaSessionAction::kSeekBackward:
  121. return l10n_util::GetStringUTF16(
  122. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_BACKWARD);
  123. case MediaSessionAction::kPlay:
  124. return l10n_util::GetStringUTF16(
  125. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY);
  126. case MediaSessionAction::kPause:
  127. return l10n_util::GetStringUTF16(
  128. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PAUSE);
  129. case MediaSessionAction::kSeekForward:
  130. return l10n_util::GetStringUTF16(
  131. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_FORWARD);
  132. case MediaSessionAction::kNextTrack:
  133. return l10n_util::GetStringUTF16(
  134. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_NEXT_TRACK);
  135. case MediaSessionAction::kEnterPictureInPicture:
  136. return l10n_util::GetStringUTF16(
  137. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_ENTER_PIP);
  138. case MediaSessionAction::kExitPictureInPicture:
  139. return l10n_util::GetStringUTF16(
  140. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_EXIT_PIP);
  141. case MediaSessionAction::kStop:
  142. case MediaSessionAction::kSkipAd:
  143. case MediaSessionAction::kSeekTo:
  144. case MediaSessionAction::kScrubTo:
  145. case MediaSessionAction::kSwitchAudioDevice:
  146. case MediaSessionAction::kToggleMicrophone:
  147. case MediaSessionAction::kToggleCamera:
  148. case MediaSessionAction::kHangUp:
  149. case MediaSessionAction::kRaise:
  150. case MediaSessionAction::kSetMute:
  151. NOTREACHED();
  152. break;
  153. }
  154. return std::u16string();
  155. }
  156. class MediaButton : public views::ImageButton {
  157. public:
  158. MediaButton(PressedCallback callback, int icon_size, gfx::Size button_size)
  159. : ImageButton(callback), icon_size_(icon_size) {
  160. SetHasInkDropActionOnClick(true);
  161. views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
  162. button_size.height() / 2);
  163. views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
  164. views::InkDrop::Get(this)->SetBaseColorCallback(base::BindRepeating(
  165. &MediaButton::GetForegroundColor, base::Unretained(this)));
  166. SetImageHorizontalAlignment(ImageButton::ALIGN_CENTER);
  167. SetImageVerticalAlignment(ImageButton::ALIGN_MIDDLE);
  168. SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
  169. SetFlipCanvasOnPaintForRTLUI(false);
  170. SetPreferredSize(button_size);
  171. }
  172. void SetButtonColor(SkColor foreground_color,
  173. SkColor foreground_disabled_color) {
  174. foreground_color_ = foreground_color;
  175. foreground_disabled_color_ = foreground_disabled_color;
  176. views::SetImageFromVectorIconWithColor(
  177. this, *GetVectorIconForMediaAction(GetActionFromButtonTag(*this)),
  178. icon_size_, foreground_color_, foreground_disabled_color_);
  179. SchedulePaint();
  180. }
  181. void set_tag(int tag) {
  182. views::ImageButton::set_tag(tag);
  183. SetTooltipText(
  184. GetAccessibleNameForMediaAction(GetActionFromButtonTag(*this)));
  185. SetAccessibleName(
  186. GetAccessibleNameForMediaAction(GetActionFromButtonTag(*this)));
  187. views::SetImageFromVectorIconWithColor(
  188. this, *GetVectorIconForMediaAction(GetActionFromButtonTag(*this)),
  189. icon_size_, foreground_color_, foreground_disabled_color_);
  190. }
  191. private:
  192. SkColor GetForegroundColor() { return foreground_color_; }
  193. SkColor foreground_color_ = gfx::kPlaceholderColor;
  194. SkColor foreground_disabled_color_ = gfx::kPlaceholderColor;
  195. int icon_size_;
  196. };
  197. } // anonymous namespace
  198. // static
  199. const char MediaNotificationViewModernImpl::kArtworkHistogramName[] =
  200. "Media.Notification.ArtworkPresent";
  201. // static
  202. const char MediaNotificationViewModernImpl::kMetadataHistogramName[] =
  203. "Media.Notification.MetadataPresent";
  204. MediaNotificationViewModernImpl::MediaNotificationViewModernImpl(
  205. MediaNotificationContainer* container,
  206. base::WeakPtr<MediaNotificationItem> item,
  207. std::unique_ptr<views::View> notification_controls_view,
  208. std::unique_ptr<views::View> notification_footer_view,
  209. int notification_width,
  210. absl::optional<NotificationTheme> theme)
  211. : container_(container), item_(std::move(item)), theme_(theme) {
  212. DCHECK(container_);
  213. DCHECK(notification_controls_view);
  214. SetPreferredSize(kMediaNotificationViewBaseSize);
  215. DCHECK(notification_width >= kMediaNotificationViewBaseSize.width())
  216. << "MediaNotificationViewModernImpl expects a width of at least "
  217. << kMediaNotificationViewBaseSize.width();
  218. auto border_insets = gfx::Insets::VH(
  219. 0, (kMediaNotificationViewBaseSize.width() - notification_width) / 2);
  220. SetBorder(views::CreateEmptyBorder(border_insets));
  221. SetLayoutManager(std::make_unique<views::BoxLayout>(
  222. views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0));
  223. bool is_cros = theme_.has_value();
  224. if (is_cros) {
  225. // We don't want the background to paint the artwork since we're painting it
  226. // ourselves.
  227. SetBackground(std::make_unique<MediaNotificationBackgroundAshImpl>(
  228. /*paint_artwork=*/false));
  229. } else {
  230. // Force the artwork width to be zero since we're painting the artwork
  231. // ourselves.
  232. SetBackground(std::make_unique<MediaNotificationBackgroundImpl>(
  233. message_center::kNotificationCornerRadius,
  234. message_center::kNotificationCornerRadius,
  235. /*artwork_max_width_pct=*/0));
  236. }
  237. UpdateCornerRadius(message_center::kNotificationCornerRadius,
  238. message_center::kNotificationCornerRadius);
  239. {
  240. // The button container contains media_controls_container_ and
  241. // notification_controls_view.
  242. auto buttons_container = std::make_unique<views::View>();
  243. buttons_container->SetPreferredSize(kButtonsContainerSize);
  244. auto* buttons_container_layout =
  245. buttons_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
  246. views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0));
  247. buttons_container_layout->set_cross_axis_alignment(
  248. views::BoxLayout::CrossAxisAlignment::kStart);
  249. // The media controls container contains buttons for media playback. This
  250. // includes play/pause, fast-forward/rewind, and skip controls.
  251. auto media_controls_container = std::make_unique<views::View>();
  252. media_controls_container->SetPreferredSize(kMediaControlsContainerSize);
  253. auto* media_controls_layout = media_controls_container->SetLayoutManager(
  254. std::make_unique<views::BoxLayout>(
  255. views::BoxLayout::Orientation::kHorizontal,
  256. kMediaControlsContainerInsets, kMediaControlsButtonSpacing));
  257. media_controls_layout->set_cross_axis_alignment(
  258. views::BoxLayout::CrossAxisAlignment::kCenter);
  259. media_controls_layout->set_main_axis_alignment(
  260. views::BoxLayout::MainAxisAlignment::kCenter);
  261. // Media controls should always be presented left-to-right,
  262. // regardless of the local UI direction.
  263. media_controls_container->SetMirrored(false);
  264. CreateMediaButton(media_controls_container.get(),
  265. MediaSessionAction::kPreviousTrack);
  266. CreateMediaButton(media_controls_container.get(),
  267. MediaSessionAction::kSeekBackward);
  268. {
  269. auto play_pause_button = std::make_unique<MediaButton>(
  270. views::Button::PressedCallback(), kPlayPauseIconSize,
  271. kPlayPauseButtonSize);
  272. play_pause_button->SetCallback(
  273. base::BindRepeating(&MediaNotificationViewModernImpl::ButtonPressed,
  274. base::Unretained(this), play_pause_button.get()));
  275. play_pause_button->set_tag(static_cast<int>(MediaSessionAction::kPlay));
  276. play_pause_button_ =
  277. media_controls_container->AddChildView(std::move(play_pause_button));
  278. }
  279. CreateMediaButton(media_controls_container.get(),
  280. MediaSessionAction::kSeekForward);
  281. CreateMediaButton(media_controls_container.get(),
  282. MediaSessionAction::kNextTrack);
  283. media_controls_container_ =
  284. buttons_container->AddChildView(std::move(media_controls_container));
  285. buttons_container_layout->SetFlexForView(media_controls_container_, 1);
  286. notification_controls_view->SetProperty(views::kMarginsKey,
  287. kNotificationControlsInsets);
  288. buttons_container->AddChildView(std::move(notification_controls_view));
  289. AddChildView(std::move(buttons_container));
  290. }
  291. auto progress_view = std::make_unique<MediaControlsProgressView>(
  292. base::BindRepeating(&MediaNotificationViewModernImpl::SeekTo,
  293. base::Unretained(this)),
  294. true /* is_modern_notification */);
  295. progress_view->SetProperty(views::kMarginsKey, kProgressBarInsets);
  296. progress_ = AddChildView(std::move(progress_view));
  297. progress_->SetVisible(true);
  298. {
  299. // The info container contains the notification artwork, the labels for the
  300. // title and artist text, the picture in picture button, and the dismiss
  301. // button.
  302. auto info_container = std::make_unique<views::View>();
  303. info_container->SetPreferredSize(kInfoContainerSize);
  304. auto* info_container_layout =
  305. info_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
  306. views::BoxLayout::Orientation::kHorizontal, kInfoContainerInsets,
  307. kInfoContainerSpacing));
  308. info_container_layout->set_cross_axis_alignment(
  309. views::BoxLayout::CrossAxisAlignment::kCenter);
  310. {
  311. auto artwork_container = std::make_unique<views::View>();
  312. artwork_container->SetPreferredSize(kArtworkSize);
  313. auto* artwork_container_layout = artwork_container->SetLayoutManager(
  314. std::make_unique<views::BoxLayout>(
  315. views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0));
  316. artwork_container_layout->set_main_axis_alignment(
  317. views::BoxLayout::MainAxisAlignment::kCenter);
  318. artwork_container_layout->set_cross_axis_alignment(
  319. views::BoxLayout::CrossAxisAlignment::kCenter);
  320. {
  321. auto artwork = std::make_unique<MediaArtworkView>(
  322. kArtworkVignetteCornerRadius, kArtworkSize, kFaviconSize);
  323. artwork_ = artwork_container->AddChildView(std::move(artwork));
  324. }
  325. artwork_container_ =
  326. info_container->AddChildView(std::move(artwork_container));
  327. }
  328. {
  329. auto labels_container = std::make_unique<views::View>();
  330. labels_container->SetPreferredSize(
  331. gfx::Size(kLabelsContainerBaseSize.width() -
  332. kNotificationControlsInsets.width(),
  333. kLabelsContainerBaseSize.height()));
  334. auto* labels_container_layout_manager =
  335. labels_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
  336. views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0));
  337. labels_container_layout_manager->set_main_axis_alignment(
  338. views::BoxLayout::MainAxisAlignment::kCenter);
  339. labels_container_layout_manager->set_cross_axis_alignment(
  340. views::BoxLayout::CrossAxisAlignment::kStart);
  341. {
  342. auto title_label = std::make_unique<views::Label>(
  343. base::EmptyString16(), views::style::CONTEXT_LABEL,
  344. views::style::STYLE_PRIMARY);
  345. title_label->SetLineHeight(kTitleArtistLineHeight);
  346. title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  347. title_label_ = labels_container->AddChildView(std::move(title_label));
  348. }
  349. {
  350. auto subtitle_label = std::make_unique<views::Label>(
  351. base::EmptyString16(), views::style::CONTEXT_LABEL,
  352. views::style::STYLE_SECONDARY);
  353. subtitle_label->SetLineHeight(18);
  354. subtitle_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  355. subtitle_label_ =
  356. labels_container->AddChildView(std::move(subtitle_label));
  357. }
  358. info_container->AddChildView(std::move(labels_container));
  359. }
  360. AddChildView(std::move(info_container));
  361. }
  362. {
  363. // This view contains pip button, mute button and cast buttons.
  364. auto util_buttons_container = std::make_unique<views::View>();
  365. util_buttons_container->SetPreferredSize(kUtilButtonsContainerSize);
  366. auto* util_buttons_layout = util_buttons_container->SetLayoutManager(
  367. std::make_unique<views::BoxLayout>(
  368. views::BoxLayout::Orientation::kHorizontal,
  369. kUtilButtonsContainerInsets, kUtilButtonsSpacing));
  370. util_buttons_layout->set_main_axis_alignment(
  371. views::BoxLayout::MainAxisAlignment::kStart);
  372. util_buttons_layout->set_cross_axis_alignment(
  373. views::BoxLayout::CrossAxisAlignment::kStretch);
  374. if (item_->SourceType() != SourceType::kCast) {
  375. // The picture-in-picture button appears directly under the media
  376. // labels.
  377. auto picture_in_picture_button = std::make_unique<MediaButton>(
  378. views::Button::PressedCallback(), kPipButtonIconSize, kPipButtonSize);
  379. picture_in_picture_button->SetCallback(base::BindRepeating(
  380. &MediaNotificationViewModernImpl::ButtonPressed,
  381. base::Unretained(this), picture_in_picture_button.get()));
  382. picture_in_picture_button->set_tag(
  383. static_cast<int>(MediaSessionAction::kEnterPictureInPicture));
  384. picture_in_picture_button_ = util_buttons_container->AddChildView(
  385. std::move(picture_in_picture_button));
  386. }
  387. if (notification_footer_view) {
  388. auto* footer_view = util_buttons_container->AddChildView(
  389. std::move(notification_footer_view));
  390. util_buttons_layout->SetFlexForView(footer_view, 1);
  391. }
  392. if (item_->SourceType() == SourceType::kCast) {
  393. auto volume_slider = std::make_unique<MediaNotificationVolumeSliderView>(
  394. base::BindRepeating(&MediaNotificationViewModernImpl::SetVolume,
  395. base::Unretained(this)));
  396. volume_slider->SetPreferredSize(kVolumeSliderSize);
  397. volume_slider_ =
  398. util_buttons_container->AddChildView(std::move(volume_slider));
  399. }
  400. auto mute_button =
  401. std::make_unique<views::ToggleImageButton>(base::BindRepeating(
  402. &MediaNotificationViewModernImpl::OnMuteButtonClicked,
  403. base::Unretained(this)));
  404. mute_button->SetPreferredSize(kMuteButtonSize);
  405. mute_button->SetImageHorizontalAlignment(
  406. views::ImageButton::HorizontalAlignment::ALIGN_CENTER);
  407. mute_button->SetImageVerticalAlignment(
  408. views::ImageButton::VerticalAlignment::ALIGN_MIDDLE);
  409. mute_button->SetTooltipText(l10n_util::GetStringUTF16(
  410. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_MUTE));
  411. mute_button->SetAccessibleName(l10n_util::GetStringUTF16(
  412. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_MUTE));
  413. mute_button_ = util_buttons_container->AddChildView(std::move(mute_button));
  414. AddChildView(std::move(util_buttons_container));
  415. }
  416. if (item_)
  417. item_->SetView(this);
  418. }
  419. MediaNotificationViewModernImpl::~MediaNotificationViewModernImpl() {
  420. if (item_)
  421. item_->SetView(nullptr);
  422. }
  423. void MediaNotificationViewModernImpl::UpdateCornerRadius(int top_radius,
  424. int bottom_radius) {
  425. if (GetMediaNotificationBackground()->UpdateCornerRadius(top_radius,
  426. bottom_radius)) {
  427. SchedulePaint();
  428. }
  429. }
  430. void MediaNotificationViewModernImpl::GetAccessibleNodeData(
  431. ui::AXNodeData* node_data) {
  432. node_data->role = ax::mojom::Role::kListItem;
  433. node_data->AddStringAttribute(
  434. ax::mojom::StringAttribute::kRoleDescription,
  435. l10n_util::GetStringUTF8(
  436. IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACCESSIBLE_NAME));
  437. if (!accessible_name_.empty())
  438. node_data->SetName(accessible_name_);
  439. }
  440. void MediaNotificationViewModernImpl::UpdateWithMediaSessionInfo(
  441. const media_session::mojom::MediaSessionInfoPtr& session_info) {
  442. bool playing =
  443. session_info && session_info->playback_state ==
  444. media_session::mojom::MediaPlaybackState::kPlaying;
  445. MediaSessionAction action =
  446. playing ? MediaSessionAction::kPause : MediaSessionAction::kPlay;
  447. play_pause_button_->set_tag(static_cast<int>(action));
  448. bool in_picture_in_picture =
  449. session_info &&
  450. session_info->picture_in_picture_state ==
  451. media_session::mojom::MediaPictureInPictureState::kInPictureInPicture;
  452. if (picture_in_picture_button_) {
  453. action = in_picture_in_picture ? MediaSessionAction::kExitPictureInPicture
  454. : MediaSessionAction::kEnterPictureInPicture;
  455. picture_in_picture_button_->set_tag(static_cast<int>(action));
  456. }
  457. UpdateActionButtonsVisibility();
  458. container_->OnMediaSessionInfoChanged(session_info);
  459. PreferredSizeChanged();
  460. Layout();
  461. SchedulePaint();
  462. }
  463. void MediaNotificationViewModernImpl::UpdateWithMediaMetadata(
  464. const media_session::MediaMetadata& metadata) {
  465. title_label_->SetText(metadata.title);
  466. subtitle_label_->SetText(metadata.source_title);
  467. accessible_name_ = GetAccessibleNameFromMetadata(metadata);
  468. // The title label should only be a11y-focusable when there is text to be
  469. // read.
  470. if (metadata.title.empty()) {
  471. title_label_->SetFocusBehavior(FocusBehavior::NEVER);
  472. } else {
  473. title_label_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
  474. RecordMetadataHistogram(Metadata::kTitle);
  475. }
  476. // The subtitle label should only be a11y-focusable when there is text to be
  477. // read.
  478. if (metadata.source_title.empty()) {
  479. subtitle_label_->SetFocusBehavior(FocusBehavior::NEVER);
  480. } else {
  481. subtitle_label_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
  482. RecordMetadataHistogram(Metadata::kSource);
  483. }
  484. RecordMetadataHistogram(Metadata::kCount);
  485. container_->OnMediaSessionMetadataChanged(metadata);
  486. PreferredSizeChanged();
  487. Layout();
  488. SchedulePaint();
  489. }
  490. void MediaNotificationViewModernImpl::UpdateWithMediaActions(
  491. const base::flat_set<media_session::mojom::MediaSessionAction>& actions) {
  492. enabled_actions_ = actions;
  493. UpdateActionButtonsVisibility();
  494. PreferredSizeChanged();
  495. Layout();
  496. SchedulePaint();
  497. }
  498. void MediaNotificationViewModernImpl::UpdateWithMediaPosition(
  499. const media_session::MediaPosition& position) {
  500. position_ = position;
  501. progress_->UpdateProgress(position);
  502. }
  503. void MediaNotificationViewModernImpl::UpdateWithMediaArtwork(
  504. const gfx::ImageSkia& image) {
  505. GetMediaNotificationBackground()->UpdateArtwork(image);
  506. UMA_HISTOGRAM_BOOLEAN(kArtworkHistogramName, !image.isNull());
  507. artwork_->SetImage(image);
  508. artwork_->SetPreferredSize(kArtworkSize);
  509. UpdateForegroundColor();
  510. container_->OnMediaArtworkChanged(image);
  511. PreferredSizeChanged();
  512. Layout();
  513. SchedulePaint();
  514. }
  515. void MediaNotificationViewModernImpl::UpdateWithFavicon(
  516. const gfx::ImageSkia& icon) {
  517. GetMediaNotificationBackground()->UpdateFavicon(icon);
  518. artwork_->SetFavicon(icon);
  519. artwork_->SetPreferredSize(kArtworkSize);
  520. UpdateForegroundColor();
  521. SchedulePaint();
  522. }
  523. void MediaNotificationViewModernImpl::OnThemeChanged() {
  524. MediaNotificationView::OnThemeChanged();
  525. UpdateForegroundColor();
  526. }
  527. void MediaNotificationViewModernImpl::UpdateDeviceSelectorAvailability(
  528. bool availability) {
  529. GetMediaNotificationBackground()->UpdateDeviceSelectorAvailability(
  530. availability);
  531. }
  532. void MediaNotificationViewModernImpl::UpdateWithMuteStatus(bool mute) {
  533. if (mute_button_) {
  534. mute_button_->SetToggled(mute);
  535. const auto mute_button_description_id =
  536. mute ? IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_UNMUTE
  537. : IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_MUTE;
  538. mute_button_->SetTooltipText(
  539. l10n_util::GetStringUTF16(mute_button_description_id));
  540. mute_button_->SetAccessibleName(
  541. l10n_util::GetStringUTF16(mute_button_description_id));
  542. }
  543. if (volume_slider_)
  544. volume_slider_->SetMute(mute);
  545. }
  546. void MediaNotificationViewModernImpl::UpdateWithVolume(float volume) {
  547. if (volume_slider_)
  548. volume_slider_->SetVolume(volume);
  549. }
  550. void MediaNotificationViewModernImpl::UpdateActionButtonsVisibility() {
  551. for (auto* view : media_controls_container_->children()) {
  552. views::Button* action_button = views::Button::AsButton(view);
  553. bool should_show = base::Contains(enabled_actions_,
  554. GetActionFromButtonTag(*action_button));
  555. bool should_invalidate = should_show != action_button->GetVisible();
  556. action_button->SetVisible(should_show);
  557. if (should_invalidate)
  558. action_button->InvalidateLayout();
  559. }
  560. if (picture_in_picture_button_) {
  561. const bool should_show_pip =
  562. base::ranges::any_of(enabled_actions_, [](MediaSessionAction action) {
  563. return action == MediaSessionAction::kEnterPictureInPicture ||
  564. action == MediaSessionAction::kExitPictureInPicture;
  565. });
  566. if (picture_in_picture_button_->GetVisible() != should_show_pip) {
  567. picture_in_picture_button_->SetVisible(should_show_pip);
  568. picture_in_picture_button_->InvalidateLayout();
  569. }
  570. }
  571. container_->OnVisibleActionsChanged(enabled_actions_);
  572. }
  573. void MediaNotificationViewModernImpl::CreateMediaButton(
  574. views::View* parent_view,
  575. MediaSessionAction action) {
  576. auto button = std::make_unique<MediaButton>(
  577. views::Button::PressedCallback(), kMediaButtonIconSize, kMediaButtonSize);
  578. button->SetCallback(
  579. base::BindRepeating(&MediaNotificationViewModernImpl::ButtonPressed,
  580. base::Unretained(this), button.get()));
  581. button->set_tag(static_cast<int>(action));
  582. parent_view->AddChildView(std::move(button));
  583. }
  584. MediaNotificationBackground*
  585. MediaNotificationViewModernImpl::GetMediaNotificationBackground() {
  586. return static_cast<MediaNotificationBackground*>(background());
  587. }
  588. void MediaNotificationViewModernImpl::UpdateForegroundColor() {
  589. if (!GetWidget())
  590. return;
  591. const SkColor background =
  592. GetMediaNotificationBackground()->GetBackgroundColor(*this);
  593. const SkColor foreground =
  594. GetMediaNotificationBackground()->GetForegroundColor(*this);
  595. const SkColor disabled_icon_color =
  596. SkColorSetA(foreground, gfx::kDisabledControlAlpha);
  597. NotificationTheme theme;
  598. if (theme_.has_value()) {
  599. theme = *theme_;
  600. } else {
  601. theme.primary_text_color = foreground;
  602. theme.secondary_text_color = foreground;
  603. theme.enabled_icon_color = foreground;
  604. theme.disabled_icon_color = disabled_icon_color;
  605. theme.separator_color = SkColorSetA(foreground, 0x1F);
  606. }
  607. artwork_->SetBackgroundColor(theme.disabled_icon_color);
  608. artwork_->SetVignetteColor(background);
  609. progress_->SetForegroundColor(theme.primary_text_color);
  610. progress_->SetBackgroundColor(theme.disabled_icon_color);
  611. progress_->SetTextColor(theme.primary_text_color);
  612. if (volume_slider_)
  613. volume_slider_->UpdateColor(theme.primary_text_color,
  614. theme.disabled_icon_color);
  615. if (mute_button_) {
  616. views::SetImageFromVectorIconWithColor(
  617. mute_button_, vector_icons::kVolumeUpIcon, kMuteButtonIconSize,
  618. theme.enabled_icon_color, theme.disabled_icon_color);
  619. views::SetToggledImageFromVectorIconWithColor(
  620. mute_button_, vector_icons::kVolumeOffIcon, kMediaButtonIconSize,
  621. theme.enabled_icon_color, theme.disabled_icon_color);
  622. }
  623. // Update the colors for the labels
  624. title_label_->SetEnabledColor(theme.primary_text_color);
  625. subtitle_label_->SetEnabledColor(theme.secondary_text_color);
  626. title_label_->SetBackgroundColor(background);
  627. subtitle_label_->SetBackgroundColor(background);
  628. // Update the colors for the toggle buttons (play/pause and
  629. // picture-in-picture)
  630. play_pause_button_->SetButtonColor(theme.enabled_icon_color,
  631. theme.disabled_icon_color);
  632. if (picture_in_picture_button_)
  633. picture_in_picture_button_->SetButtonColor(theme.enabled_icon_color,
  634. theme.disabled_icon_color);
  635. // Update the colors for the media control buttons.
  636. for (views::View* child : media_controls_container_->children()) {
  637. // Skip the play pause button since it is a special case.
  638. if (child == play_pause_button_)
  639. continue;
  640. MediaButton* button = static_cast<MediaButton*>(child);
  641. button->SetButtonColor(theme.enabled_icon_color, theme.disabled_icon_color);
  642. }
  643. SchedulePaint();
  644. container_->OnColorsChanged(theme.enabled_icon_color,
  645. theme.disabled_icon_color, background);
  646. }
  647. void MediaNotificationViewModernImpl::ButtonPressed(views::Button* button) {
  648. if (item_)
  649. item_->OnMediaSessionActionButtonPressed(GetActionFromButtonTag(*button));
  650. }
  651. void MediaNotificationViewModernImpl::SeekTo(double seek_progress) {
  652. item_->SeekTo(seek_progress * position_.duration());
  653. }
  654. void MediaNotificationViewModernImpl::OnMuteButtonClicked() {
  655. item_->SetMute(!mute_button_->GetToggled());
  656. }
  657. void MediaNotificationViewModernImpl::SetVolume(float volume) {
  658. item_->SetVolume(volume);
  659. item_->SetMute(volume == 0);
  660. }
  661. views::Button*
  662. MediaNotificationViewModernImpl::picture_in_picture_button_for_testing() const {
  663. return picture_in_picture_button_;
  664. }
  665. BEGIN_METADATA(MediaNotificationViewModernImpl, views::View)
  666. END_METADATA
  667. } // namespace media_message_center