summaryrefslogtreecommitdiff
path: root/chromium/components/media_message_center
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-11-18 16:35:47 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-11-18 15:45:54 +0000
commit32f5a1c56531e4210bc4cf8d8c7825d66e081888 (patch)
treeeeeec6822f4d738d8454525233fd0e2e3a659e6d /chromium/components/media_message_center
parent99677208ff3b216fdfec551fbe548da5520cd6fb (diff)
downloadqtwebengine-chromium-32f5a1c56531e4210bc4cf8d8c7825d66e081888.tar.gz
BASELINE: Update Chromium to 87.0.4280.67
Change-Id: Ib157360be8c2ffb2c73125751a89f60e049c1d54 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/media_message_center')
-rw-r--r--chromium/components/media_message_center/BUILD.gn11
-rw-r--r--chromium/components/media_message_center/media_notification_background.h69
-rw-r--r--chromium/components/media_message_center/media_notification_background_ash_impl.cc99
-rw-r--r--chromium/components/media_message_center/media_notification_background_ash_impl.h46
-rw-r--r--chromium/components/media_message_center/media_notification_background_ash_impl_unittest.cc50
-rw-r--r--chromium/components/media_message_center/media_notification_background_impl.cc (renamed from chromium/components/media_message_center/media_notification_background.cc)85
-rw-r--r--chromium/components/media_message_center/media_notification_background_impl.h86
-rw-r--r--chromium/components/media_message_center/media_notification_background_impl_unittest.cc (renamed from chromium/components/media_message_center/media_notification_background_unittest.cc)92
-rw-r--r--chromium/components/media_message_center/media_notification_view.h1
-rw-r--r--chromium/components/media_message_center/media_notification_view_impl.cc23
-rw-r--r--chromium/components/media_message_center/media_notification_view_impl.h11
-rw-r--r--chromium/components/media_message_center/media_notification_view_impl_unittest.cc29
-rw-r--r--chromium/components/media_message_center/media_notification_view_modern_impl.cc595
-rw-r--r--chromium/components/media_message_center/media_notification_view_modern_impl.h154
-rw-r--r--chromium/components/media_message_center/media_notification_view_modern_impl_unittest.cc1074
-rw-r--r--chromium/components/media_message_center/vector_icons/BUILD.gn8
16 files changed, 2288 insertions, 145 deletions
diff --git a/chromium/components/media_message_center/BUILD.gn b/chromium/components/media_message_center/BUILD.gn
index 5fb78edf6f3..7becd24bbc8 100644
--- a/chromium/components/media_message_center/BUILD.gn
+++ b/chromium/components/media_message_center/BUILD.gn
@@ -6,8 +6,11 @@ component("media_message_center") {
sources = [
"media_controls_progress_view.cc",
"media_controls_progress_view.h",
- "media_notification_background.cc",
"media_notification_background.h",
+ "media_notification_background_ash_impl.cc",
+ "media_notification_background_ash_impl.h",
+ "media_notification_background_impl.cc",
+ "media_notification_background_impl.h",
"media_notification_constants.cc",
"media_notification_constants.h",
"media_notification_container.h",
@@ -19,6 +22,8 @@ component("media_message_center") {
"media_notification_view.h",
"media_notification_view_impl.cc",
"media_notification_view_impl.h",
+ "media_notification_view_modern_impl.cc",
+ "media_notification_view_modern_impl.h",
"media_session_notification_item.cc",
"media_session_notification_item.h",
]
@@ -45,8 +50,10 @@ source_set("unit_tests") {
sources = [
"media_controls_progress_view_unittest.cc",
- "media_notification_background_unittest.cc",
+ "media_notification_background_ash_impl_unittest.cc",
+ "media_notification_background_impl_unittest.cc",
"media_notification_view_impl_unittest.cc",
+ "media_notification_view_modern_impl_unittest.cc",
]
deps = [
diff --git a/chromium/components/media_message_center/media_notification_background.h b/chromium/components/media_message_center/media_notification_background.h
index c3ffec25b8e..da45dd3b200 100644
--- a/chromium/components/media_message_center/media_notification_background.h
+++ b/chromium/components/media_message_center/media_notification_background.h
@@ -1,20 +1,14 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// 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.
#ifndef COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_H_
#define COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_H_
-#include <vector>
-
-#include "base/component_export.h"
-#include "base/optional.h"
-#include "ui/gfx/image/image_skia.h"
#include "ui/views/background.h"
namespace gfx {
-class Rect;
-class Size;
+class ImageSkia;
} // namespace gfx
namespace views {
@@ -23,58 +17,25 @@ class View;
namespace media_message_center {
-// MediaNotificationBackground draws a custom background for the media
-// notification showing the artwork clipped to a rounded rectangle faded into a
-// background color.
-class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationBackground
- : public views::Background {
+// Interface for media notification view's background.
+class MediaNotificationBackground : public views::Background {
public:
- MediaNotificationBackground(int top_radius,
- int bottom_radius,
- double artwork_max_width_pct);
- ~MediaNotificationBackground() override;
-
- // views::Background
- void Paint(gfx::Canvas* canvas, views::View* view) const override;
-
- void UpdateArtwork(const gfx::ImageSkia& image);
- bool UpdateCornerRadius(int top_radius, int bottom_radius);
- bool UpdateArtworkMaxWidthPct(double max_width_pct);
- void UpdateFavicon(const gfx::ImageSkia& icon);
-
- SkColor GetBackgroundColor(const views::View& owner) const;
- SkColor GetForegroundColor(const views::View& owner) const;
-
- private:
- friend class MediaNotificationBackgroundTest;
- friend class MediaNotificationViewImplTest;
- FRIEND_TEST_ALL_PREFIXES(MediaNotificationBackgroundRTLTest,
- BoundsSanityCheck);
-
- // Shade factor used on favicon dominant color before set as background color.
- static constexpr double kBackgroundFaviconColorShadeFactor = 0.35;
+ // views::Background.
+ void Paint(gfx::Canvas* canvas, views::View* view) const override = 0;
- int GetArtworkWidth(const gfx::Size& view_size) const;
- int GetArtworkVisibleWidth(const gfx::Size& view_size) const;
- gfx::Rect GetArtworkBounds(const views::View& owner) const;
- gfx::Rect GetFilledBackgroundBounds(const views::View& owner) const;
- gfx::Rect GetGradientBounds(const views::View& owner) const;
- SkPoint GetGradientStartPoint(const gfx::Rect& draw_bounds) const;
- SkPoint GetGradientEndPoint(const gfx::Rect& draw_bounds) const;
- SkColor GetDefaultBackgroundColor(const views::View& owner) const;
- void UpdateColorsInternal();
+ virtual void UpdateArtwork(const gfx::ImageSkia& image) = 0;
- int top_radius_;
- int bottom_radius_;
+ // Return true if corner radius is successfully updated.
+ virtual bool UpdateCornerRadius(int top_radius, int bottom_radius) = 0;
- gfx::ImageSkia favicon_;
- gfx::ImageSkia artwork_;
- double artwork_max_width_pct_;
+ // Retirm true if artwork max with percentage is successfully updated.
+ virtual bool UpdateArtworkMaxWidthPct(double max_width_pct) = 0;
- base::Optional<SkColor> background_color_;
- base::Optional<SkColor> foreground_color_;
+ virtual void UpdateFavicon(const gfx::ImageSkia& icon) = 0;
+ virtual void UpdateDeviceSelectorAvailability(bool availability) = 0;
- DISALLOW_COPY_AND_ASSIGN(MediaNotificationBackground);
+ virtual SkColor GetBackgroundColor(const views::View& ownser) const = 0;
+ virtual SkColor GetForegroundColor(const views::View& ownser) const = 0;
};
} // namespace media_message_center
diff --git a/chromium/components/media_message_center/media_notification_background_ash_impl.cc b/chromium/components/media_message_center/media_notification_background_ash_impl.cc
new file mode 100644
index 00000000000..091d78fe26c
--- /dev/null
+++ b/chromium/components/media_message_center/media_notification_background_ash_impl.cc
@@ -0,0 +1,99 @@
+// 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_background_ash_impl.h"
+
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/view.h"
+
+namespace media_message_center {
+
+namespace {
+
+constexpr SkColor kBackgroundColor = SK_ColorTRANSPARENT;
+constexpr SkColor kForegroundColor = SK_ColorWHITE;
+
+constexpr gfx::Size kArtworkSize(80, 80);
+constexpr int kArtworkBottomMargin = 16;
+constexpr int kArtworkRightMargin = 16;
+constexpr int kArtworkCornerRadius = 4;
+
+gfx::Size ScaleToFitSize(const gfx::Size& image_size) {
+ if ((image_size.width() > kArtworkSize.width() ||
+ image_size.height() > kArtworkSize.height()) ||
+ (image_size.width() < kArtworkSize.width() &&
+ image_size.height() < kArtworkSize.height())) {
+ const float scale = std::min(
+ kArtworkSize.width() / static_cast<float>(image_size.width()),
+ kArtworkSize.height() / static_cast<float>(image_size.height()));
+ return gfx::ScaleToFlooredSize(image_size, scale);
+ }
+
+ return image_size;
+}
+
+} // namespace
+
+gfx::Rect MediaNotificationBackgroundAshImpl::GetArtworkBounds(
+ const gfx::Rect& view_bounds) const {
+ gfx::Size target_size = ScaleToFitSize(artwork_.size());
+
+ int vertical_offset = (kArtworkSize.height() - target_size.height()) / 2;
+ int horizontal_offset = (kArtworkSize.width() - target_size.width()) / 2;
+
+ return gfx::Rect(view_bounds.right() - kArtworkRightMargin -
+ kArtworkSize.width() + horizontal_offset,
+ view_bounds.bottom() - kArtworkBottomMargin -
+ kArtworkSize.height() + vertical_offset,
+ target_size.width(), target_size.height());
+}
+
+void MediaNotificationBackgroundAshImpl::Paint(gfx::Canvas* canvas,
+ views::View* view) const {
+ gfx::Rect source_bounds(0, 0, artwork_.width(), artwork_.height());
+ gfx::Rect target_bounds = GetArtworkBounds(view->GetContentsBounds());
+
+ SkPath path;
+ path.addRoundRect(gfx::RectToSkRect(target_bounds), kArtworkCornerRadius,
+ kArtworkCornerRadius);
+
+ canvas->ClipPath(path, true);
+
+ canvas->DrawImageInt(
+ artwork_, source_bounds.x(), source_bounds.y(), source_bounds.width(),
+ source_bounds.height(), target_bounds.x(), target_bounds.y(),
+ target_bounds.width(), target_bounds.height(), false /* filter */);
+}
+
+void MediaNotificationBackgroundAshImpl::UpdateArtwork(
+ const gfx::ImageSkia& image) {
+ if (artwork_.BackedBySameObjectAs(image))
+ return;
+
+ artwork_ = image;
+}
+
+bool MediaNotificationBackgroundAshImpl::UpdateCornerRadius(int top_radius,
+ int bottom_radius) {
+ return false;
+}
+
+bool MediaNotificationBackgroundAshImpl::UpdateArtworkMaxWidthPct(
+ double max_width_pct) {
+ return false;
+}
+
+SkColor MediaNotificationBackgroundAshImpl::GetBackgroundColor(
+ const views::View& owner) const {
+ return kBackgroundColor;
+}
+
+SkColor MediaNotificationBackgroundAshImpl::GetForegroundColor(
+ const views::View& owner) const {
+ return kForegroundColor;
+}
+
+} // namespace media_message_center
diff --git a/chromium/components/media_message_center/media_notification_background_ash_impl.h b/chromium/components/media_message_center/media_notification_background_ash_impl.h
new file mode 100644
index 00000000000..6742e6f4eff
--- /dev/null
+++ b/chromium/components/media_message_center/media_notification_background_ash_impl.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_ASH_IMPL_H_
+#define COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_ASH_IMPL_H_
+
+#include "components/media_message_center/media_notification_background.h"
+
+#include "base/component_export.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace media_message_center {
+
+// MediaNotificationBackground for CrOS media notifications.
+class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationBackgroundAshImpl
+ : public MediaNotificationBackground {
+ public:
+ MediaNotificationBackgroundAshImpl() = default;
+ MediaNotificationBackgroundAshImpl(
+ const MediaNotificationBackgroundAshImpl&) = delete;
+ MediaNotificationBackgroundAshImpl& operator=(
+ const MediaNotificationBackgroundAshImpl&) = delete;
+ ~MediaNotificationBackgroundAshImpl() override = default;
+
+ // MediaNotificationBackground implementations.
+ void Paint(gfx::Canvas* canvas, views::View* view) const override;
+ void UpdateArtwork(const gfx::ImageSkia& image) override;
+ bool UpdateCornerRadius(int top_radius, int bottom_radius) override;
+ bool UpdateArtworkMaxWidthPct(double max_width_pct) override;
+ void UpdateFavicon(const gfx::ImageSkia& icon) override {}
+ void UpdateDeviceSelectorAvailability(bool availability) override {}
+ SkColor GetBackgroundColor(const views::View& owner) const override;
+ SkColor GetForegroundColor(const views::View& owner) const override;
+
+ private:
+ friend class MediaNotificationBackgroundAshImplTest;
+
+ gfx::Rect GetArtworkBounds(const gfx::Rect& view_bounds) const;
+
+ gfx::ImageSkia artwork_;
+};
+
+} // namespace media_message_center
+
+#endif // COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_ASH_IMPL_H_
diff --git a/chromium/components/media_message_center/media_notification_background_ash_impl_unittest.cc b/chromium/components/media_message_center/media_notification_background_ash_impl_unittest.cc
new file mode 100644
index 00000000000..87bc0bfe085
--- /dev/null
+++ b/chromium/components/media_message_center/media_notification_background_ash_impl_unittest.cc
@@ -0,0 +1,50 @@
+// 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_background_ash_impl.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media_message_center {
+
+class MediaNotificationBackgroundAshImplTest : public testing::Test {
+ public:
+ MediaNotificationBackgroundAshImplTest() = default;
+ ~MediaNotificationBackgroundAshImplTest() override = default;
+
+ void SetUp() override {
+ background_ = std::make_unique<MediaNotificationBackgroundAshImpl>();
+ }
+
+ void TearDown() override { background_.reset(); }
+
+ gfx::ImageSkia CreateTestImage(int width, int height) {
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(width, height);
+ return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+ }
+
+ gfx::Rect GetArtworkBounds(gfx::Rect view_rect) {
+ return background_->GetArtworkBounds(view_rect);
+ }
+
+ MediaNotificationBackgroundAshImpl* background() { return background_.get(); }
+
+ private:
+ std::unique_ptr<MediaNotificationBackgroundAshImpl> background_;
+};
+
+TEST_F(MediaNotificationBackgroundAshImplTest, ArtworkBoundsTest) {
+ gfx::Rect parent_bounds(0, 0, 100, 100);
+ background()->UpdateArtwork(CreateTestImage(160, 60));
+ EXPECT_EQ(GetArtworkBounds(parent_bounds).size(), gfx::Size(80, 30));
+
+ background()->UpdateArtwork(CreateTestImage(60, 160));
+ EXPECT_EQ(GetArtworkBounds(parent_bounds).size(), gfx::Size(30, 80));
+
+ background()->UpdateArtwork(CreateTestImage(40, 20));
+ EXPECT_EQ(GetArtworkBounds(parent_bounds).size(), gfx::Size(80, 40));
+}
+
+} // namespace media_message_center
diff --git a/chromium/components/media_message_center/media_notification_background.cc b/chromium/components/media_message_center/media_notification_background_impl.cc
index 97cadb59af3..d937748b84c 100644
--- a/chromium/components/media_message_center/media_notification_background.cc
+++ b/chromium/components/media_message_center/media_notification_background_impl.cc
@@ -2,7 +2,7 @@
// 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_background.h"
+#include "components/media_message_center/media_notification_background_impl.h"
#include <algorithm>
#include <vector>
@@ -233,7 +233,7 @@ base::Optional<SkColor> GetNotificationForegroundColor(
} // namespace
-MediaNotificationBackground::MediaNotificationBackground(
+MediaNotificationBackgroundImpl::MediaNotificationBackgroundImpl(
int top_radius,
int bottom_radius,
double artwork_max_width_pct)
@@ -241,10 +241,10 @@ MediaNotificationBackground::MediaNotificationBackground(
bottom_radius_(bottom_radius),
artwork_max_width_pct_(artwork_max_width_pct) {}
-MediaNotificationBackground::~MediaNotificationBackground() = default;
+MediaNotificationBackgroundImpl::~MediaNotificationBackgroundImpl() = default;
-void MediaNotificationBackground::Paint(gfx::Canvas* canvas,
- views::View* view) const {
+void MediaNotificationBackgroundImpl::Paint(gfx::Canvas* canvas,
+ views::View* view) const {
DCHECK(view);
gfx::ScopedCanvas scoped_canvas(canvas);
@@ -302,9 +302,29 @@ void MediaNotificationBackground::Paint(gfx::Canvas* canvas,
canvas->DrawRect(draw_bounds, flags);
}
+
+ if (audio_device_selector_availability_) {
+ // Draw a gradient to fade the color background of the audio device picker
+ // and the image together.
+ gfx::Rect draw_bounds = GetBottomGradientBounds(*view);
+
+ const SkColor colors[2] = {
+ background_color, SkColorSetA(background_color, SK_AlphaTRANSPARENT)};
+ const SkPoint points[2] = {gfx::PointToSkPoint(draw_bounds.bottom_center()),
+ gfx::PointToSkPoint(draw_bounds.top_center())};
+
+ cc::PaintFlags flags;
+ flags.setAntiAlias(true);
+ flags.setStyle(cc::PaintFlags::kFill_Style);
+ flags.setShader(cc::PaintShader::MakeLinearGradient(points, colors, nullptr,
+ 2, SkTileMode::kClamp));
+
+ canvas->DrawRect(draw_bounds, flags);
+ }
}
-void MediaNotificationBackground::UpdateArtwork(const gfx::ImageSkia& image) {
+void MediaNotificationBackgroundImpl::UpdateArtwork(
+ const gfx::ImageSkia& image) {
if (artwork_.BackedBySameObjectAs(image))
return;
@@ -313,8 +333,8 @@ void MediaNotificationBackground::UpdateArtwork(const gfx::ImageSkia& image) {
UpdateColorsInternal();
}
-bool MediaNotificationBackground::UpdateCornerRadius(int top_radius,
- int bottom_radius) {
+bool MediaNotificationBackgroundImpl::UpdateCornerRadius(int top_radius,
+ int bottom_radius) {
if (top_radius_ == top_radius && bottom_radius_ == bottom_radius)
return false;
@@ -323,7 +343,7 @@ bool MediaNotificationBackground::UpdateCornerRadius(int top_radius,
return true;
}
-bool MediaNotificationBackground::UpdateArtworkMaxWidthPct(
+bool MediaNotificationBackgroundImpl::UpdateArtworkMaxWidthPct(
double max_width_pct) {
if (artwork_max_width_pct_ == max_width_pct)
return false;
@@ -332,7 +352,8 @@ bool MediaNotificationBackground::UpdateArtworkMaxWidthPct(
return true;
}
-void MediaNotificationBackground::UpdateFavicon(const gfx::ImageSkia& icon) {
+void MediaNotificationBackgroundImpl::UpdateFavicon(
+ const gfx::ImageSkia& icon) {
if (favicon_.BackedBySameObjectAs(icon))
return;
@@ -344,14 +365,22 @@ void MediaNotificationBackground::UpdateFavicon(const gfx::ImageSkia& icon) {
UpdateColorsInternal();
}
-SkColor MediaNotificationBackground::GetBackgroundColor(
+void MediaNotificationBackgroundImpl::UpdateDeviceSelectorAvailability(
+ bool availability) {
+ if (audio_device_selector_availability_ == availability)
+ return;
+
+ audio_device_selector_availability_ = availability;
+}
+
+SkColor MediaNotificationBackgroundImpl::GetBackgroundColor(
const views::View& owner) const {
if (background_color_.has_value())
return *background_color_;
return GetDefaultBackgroundColor(owner);
}
-SkColor MediaNotificationBackground::GetForegroundColor(
+SkColor MediaNotificationBackgroundImpl::GetForegroundColor(
const views::View& owner) const {
const SkColor foreground =
foreground_color_.has_value()
@@ -364,7 +393,7 @@ SkColor MediaNotificationBackground::GetForegroundColor(
.color;
}
-int MediaNotificationBackground::GetArtworkWidth(
+int MediaNotificationBackgroundImpl::GetArtworkWidth(
const gfx::Size& view_size) const {
if (artwork_.isNull())
return 0;
@@ -375,14 +404,14 @@ int MediaNotificationBackground::GetArtworkWidth(
return ceil(view_size.height() * aspect_ratio);
}
-int MediaNotificationBackground::GetArtworkVisibleWidth(
+int MediaNotificationBackgroundImpl::GetArtworkVisibleWidth(
const gfx::Size& view_size) const {
// The artwork should only take up a maximum percentage of the notification.
return std::min(GetArtworkWidth(view_size),
(int)ceil(view_size.width() * artwork_max_width_pct_));
}
-gfx::Rect MediaNotificationBackground::GetArtworkBounds(
+gfx::Rect MediaNotificationBackgroundImpl::GetArtworkBounds(
const views::View& owner) const {
const gfx::Rect& view_bounds = owner.GetContentsBounds();
int width = GetArtworkWidth(view_bounds.size());
@@ -399,7 +428,7 @@ gfx::Rect MediaNotificationBackground::GetArtworkBounds(
view_bounds.height()));
}
-gfx::Rect MediaNotificationBackground::GetFilledBackgroundBounds(
+gfx::Rect MediaNotificationBackgroundImpl::GetFilledBackgroundBounds(
const views::View& owner) const {
// The filled background should take up the full notification except the area
// taken up by the artwork.
@@ -409,7 +438,7 @@ gfx::Rect MediaNotificationBackground::GetFilledBackgroundBounds(
return owner.GetMirroredRect(bounds);
}
-gfx::Rect MediaNotificationBackground::GetGradientBounds(
+gfx::Rect MediaNotificationBackgroundImpl::GetGradientBounds(
const views::View& owner) const {
if (artwork_.isNull())
return gfx::Rect(0, 0, 0, 0);
@@ -421,25 +450,39 @@ gfx::Rect MediaNotificationBackground::GetGradientBounds(
view_bounds.y(), kMediaImageGradientWidth, view_bounds.height()));
}
-SkPoint MediaNotificationBackground::GetGradientStartPoint(
+gfx::Rect MediaNotificationBackgroundImpl::GetBottomGradientBounds(
+ const views::View& owner) const {
+ if (artwork_.isNull())
+ return gfx::Rect(0, 0, 0, 0);
+
+ const gfx::Rect& view_bounds = owner.GetContentsBounds();
+ return owner.GetMirroredRect(gfx::Rect(
+ gfx::Point(
+ view_bounds.width() - GetArtworkVisibleWidth(view_bounds.size()),
+ view_bounds.bottom() - kMediaImageGradientWidth),
+ gfx::Size(GetArtworkVisibleWidth(view_bounds.size()),
+ kMediaImageGradientWidth)));
+}
+
+SkPoint MediaNotificationBackgroundImpl::GetGradientStartPoint(
const gfx::Rect& draw_bounds) const {
return gfx::PointToSkPoint(base::i18n::IsRTL() ? draw_bounds.right_center()
: draw_bounds.left_center());
}
-SkPoint MediaNotificationBackground::GetGradientEndPoint(
+SkPoint MediaNotificationBackgroundImpl::GetGradientEndPoint(
const gfx::Rect& draw_bounds) const {
return gfx::PointToSkPoint(base::i18n::IsRTL() ? draw_bounds.left_center()
: draw_bounds.right_center());
}
-SkColor MediaNotificationBackground::GetDefaultBackgroundColor(
+SkColor MediaNotificationBackgroundImpl::GetDefaultBackgroundColor(
const views::View& owner) const {
return owner.GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_BubbleBackground);
}
-void MediaNotificationBackground::UpdateColorsInternal() {
+void MediaNotificationBackgroundImpl::UpdateColorsInternal() {
// If there is an artwork, it should be used.
// If there is no artwork, neither a favicon, the artwork bitmap will be used
// which is going to be a null bitmap and produce a default value.
diff --git a/chromium/components/media_message_center/media_notification_background_impl.h b/chromium/components/media_message_center/media_notification_background_impl.h
new file mode 100644
index 00000000000..beb99f826b6
--- /dev/null
+++ b/chromium/components/media_message_center/media_notification_background_impl.h
@@ -0,0 +1,86 @@
+// Copyright 2019 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.
+
+#ifndef COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_IMPL_H_
+#define COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_IMPL_H_
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/optional.h"
+#include "components/media_message_center/media_notification_background.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace gfx {
+class Rect;
+class Size;
+} // namespace gfx
+
+namespace views {
+class View;
+} // namespace views
+
+namespace media_message_center {
+
+// MediaNotificationBackground draws a custom background for the media
+// notification showing the artwork clipped to a rounded rectangle faded into a
+// background color.
+class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationBackgroundImpl
+ : public MediaNotificationBackground {
+ public:
+ MediaNotificationBackgroundImpl(int top_radius,
+ int bottom_radius,
+ double artwork_max_width_pct);
+ ~MediaNotificationBackgroundImpl() override;
+
+ // views::Background
+ void Paint(gfx::Canvas* canvas, views::View* view) const override;
+
+ void UpdateArtwork(const gfx::ImageSkia& image) override;
+ bool UpdateCornerRadius(int top_radius, int bottom_radius) override;
+ bool UpdateArtworkMaxWidthPct(double max_width_pct) override;
+ void UpdateFavicon(const gfx::ImageSkia& icon) override;
+ void UpdateDeviceSelectorAvailability(bool availability) override;
+
+ SkColor GetBackgroundColor(const views::View& owner) const override;
+ SkColor GetForegroundColor(const views::View& owner) const override;
+
+ private:
+ friend class MediaNotificationBackgroundImplTest;
+ friend class MediaNotificationViewImplTest;
+ friend class MediaNotificationViewModernImplTest;
+ FRIEND_TEST_ALL_PREFIXES(MediaNotificationBackgroundImplRTLTest,
+ BoundsSanityCheck);
+
+ // Shade factor used on favicon dominant color before set as background color.
+ static constexpr double kBackgroundFaviconColorShadeFactor = 0.35;
+
+ int GetArtworkWidth(const gfx::Size& view_size) const;
+ int GetArtworkVisibleWidth(const gfx::Size& view_size) const;
+ gfx::Rect GetArtworkBounds(const views::View& owner) const;
+ gfx::Rect GetFilledBackgroundBounds(const views::View& owner) const;
+ gfx::Rect GetGradientBounds(const views::View& owner) const;
+ gfx::Rect GetBottomGradientBounds(const views::View& owner) const;
+ SkPoint GetGradientStartPoint(const gfx::Rect& draw_bounds) const;
+ SkPoint GetGradientEndPoint(const gfx::Rect& draw_bounds) const;
+ SkColor GetDefaultBackgroundColor(const views::View& owner) const;
+ void UpdateColorsInternal();
+
+ int top_radius_;
+ int bottom_radius_;
+
+ gfx::ImageSkia favicon_;
+ gfx::ImageSkia artwork_;
+ double artwork_max_width_pct_;
+ bool audio_device_selector_availability_ = false;
+
+ base::Optional<SkColor> background_color_;
+ base::Optional<SkColor> foreground_color_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaNotificationBackgroundImpl);
+};
+
+} // namespace media_message_center
+
+#endif // COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_BACKGROUND_IMPL_H_
diff --git a/chromium/components/media_message_center/media_notification_background_unittest.cc b/chromium/components/media_message_center/media_notification_background_impl_unittest.cc
index 1374a9a09e1..75213a3147f 100644
--- a/chromium/components/media_message_center/media_notification_background_unittest.cc
+++ b/chromium/components/media_message_center/media_notification_background_impl_unittest.cc
@@ -2,7 +2,7 @@
// 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_background.h"
+#include "components/media_message_center/media_notification_background_impl.h"
#include <memory>
@@ -84,22 +84,23 @@ gfx::ImageSkia CreateTestBackgroundImage(SkColor color) {
} // namespace
-class MediaNotificationBackgroundTest : public testing::Test {
+class MediaNotificationBackgroundImplTest : public testing::Test {
public:
- MediaNotificationBackgroundTest() = default;
- ~MediaNotificationBackgroundTest() override = default;
+ MediaNotificationBackgroundImplTest() = default;
+ ~MediaNotificationBackgroundImplTest() override = default;
void SetUp() override {
- background_ = std::make_unique<MediaNotificationBackground>(10, 10, 0.1);
+ background_ =
+ std::make_unique<MediaNotificationBackgroundImpl>(10, 10, 0.1);
EXPECT_FALSE(GetBackgroundColor().has_value());
}
- void TearDown() override {
- background_.reset();
- }
+ void TearDown() override { background_.reset(); }
- MediaNotificationBackground* background() const { return background_.get(); }
+ MediaNotificationBackgroundImpl* background() const {
+ return background_.get();
+ }
base::Optional<SkColor> GetBackgroundColor() const {
return background_->background_color_;
@@ -110,38 +111,39 @@ class MediaNotificationBackgroundTest : public testing::Test {
}
double GetBackgroundFaviconColorShadeFactor() const {
- return MediaNotificationBackground::kBackgroundFaviconColorShadeFactor;
+ return MediaNotificationBackgroundImpl::kBackgroundFaviconColorShadeFactor;
}
private:
- std::unique_ptr<MediaNotificationBackground> background_;
+ std::unique_ptr<MediaNotificationBackgroundImpl> background_;
- DISALLOW_COPY_AND_ASSIGN(MediaNotificationBackgroundTest);
+ DISALLOW_COPY_AND_ASSIGN(MediaNotificationBackgroundImplTest);
};
// If we have no artwork then we should use the default background color.
-TEST_F(MediaNotificationBackgroundTest, DeriveBackgroundColor_NoArtwork) {
+TEST_F(MediaNotificationBackgroundImplTest, DeriveBackgroundColor_NoArtwork) {
background()->UpdateArtwork(gfx::ImageSkia());
EXPECT_FALSE(GetBackgroundColor().has_value());
}
// If we have artwork with no popular color then we should use the default
// background color.
-TEST_F(MediaNotificationBackgroundTest, DeriveBackgroundColor_NoPopularColor) {
+TEST_F(MediaNotificationBackgroundImplTest,
+ DeriveBackgroundColor_NoPopularColor) {
background()->UpdateArtwork(CreateTestBackgroundImage(SK_ColorTRANSPARENT));
EXPECT_FALSE(GetBackgroundColor().has_value());
}
// If the most popular color is not white or black then we should use that
// color.
-TEST_F(MediaNotificationBackgroundTest,
+TEST_F(MediaNotificationBackgroundImplTest,
DeriveBackgroundColor_PopularNonWhiteBlackColor) {
constexpr SkColor kTestColor = SK_ColorYELLOW;
background()->UpdateArtwork(CreateTestBackgroundImage(kTestColor));
EXPECT_EQ(kTestColor, GetBackgroundColor());
}
-TEST_F(MediaNotificationBackgroundTest,
+TEST_F(MediaNotificationBackgroundImplTest,
DeriveBackgroundColor_NoArtworkAfterHavingOne) {
constexpr SkColor kTestColor = SK_ColorYELLOW;
background()->UpdateArtwork(CreateTestBackgroundImage(kTestColor));
@@ -152,7 +154,7 @@ TEST_F(MediaNotificationBackgroundTest,
}
// Favicons should be used when available but have a shade applying to them.
-TEST_F(MediaNotificationBackgroundTest,
+TEST_F(MediaNotificationBackgroundImplTest,
DeriveBackgroundColor_PopularNonWhiteBlackColorFavicon) {
constexpr SkColor kTestColor = SK_ColorYELLOW;
background()->UpdateFavicon(CreateTestBackgroundImage(kTestColor));
@@ -163,7 +165,7 @@ TEST_F(MediaNotificationBackgroundTest,
EXPECT_EQ(expected_color, GetBackgroundColor());
}
-TEST_F(MediaNotificationBackgroundTest,
+TEST_F(MediaNotificationBackgroundImplTest,
DeriveBackgroundColor_NoFaviconAfterHavingOne) {
constexpr SkColor kTestColor = SK_ColorYELLOW;
background()->UpdateFavicon(CreateTestBackgroundImage(kTestColor));
@@ -177,7 +179,7 @@ TEST_F(MediaNotificationBackgroundTest,
EXPECT_FALSE(GetBackgroundColor().has_value());
}
-TEST_F(MediaNotificationBackgroundTest,
+TEST_F(MediaNotificationBackgroundImplTest,
DeriveBackgroundColor_FaviconSetThenArtwork) {
constexpr SkColor kArtworkColor = SK_ColorYELLOW;
constexpr SkColor kFaviconColor = SK_ColorRED;
@@ -188,7 +190,7 @@ TEST_F(MediaNotificationBackgroundTest,
EXPECT_EQ(kArtworkColor, GetBackgroundColor());
}
-TEST_F(MediaNotificationBackgroundTest,
+TEST_F(MediaNotificationBackgroundImplTest,
DeriveBackgroundColor_ArtworkSetThenFavicon) {
constexpr SkColor kArtworkColor = SK_ColorYELLOW;
constexpr SkColor kFaviconColor = SK_ColorRED;
@@ -199,7 +201,7 @@ TEST_F(MediaNotificationBackgroundTest,
EXPECT_EQ(kArtworkColor, GetBackgroundColor());
}
-TEST_F(MediaNotificationBackgroundTest,
+TEST_F(MediaNotificationBackgroundImplTest,
DeriveBackgroundColor_SetAndRemoveArtworkWithFavicon) {
constexpr SkColor kArtworkColor = SK_ColorYELLOW;
constexpr SkColor kFaviconColor = SK_ColorRED;
@@ -215,17 +217,17 @@ TEST_F(MediaNotificationBackgroundTest,
EXPECT_EQ(expected_color, GetBackgroundColor());
}
-TEST_F(MediaNotificationBackgroundTest, GetBackgroundColorRespectsTheme) {
+TEST_F(MediaNotificationBackgroundImplTest, GetBackgroundColorRespectsTheme) {
TestDarkTheme dark_theme;
views::View owner;
owner.SetNativeThemeForTesting(&dark_theme);
EXPECT_EQ(kDarkBackgroundColor, background()->GetBackgroundColor(owner));
}
-// MediaNotificationBackgroundBlackWhiteTest will repeat these tests with a
+// MediaNotificationBackgroundImplBlackWhiteTest will repeat these tests with a
// parameter that is either black or white.
-class MediaNotificationBackgroundBlackWhiteTest
- : public MediaNotificationBackgroundTest,
+class MediaNotificationBackgroundImplBlackWhiteTest
+ : public MediaNotificationBackgroundImplTest,
public testing::WithParamInterface<SkColor> {
public:
bool IsBlack() const { return GetParam() == SK_ColorBLACK; }
@@ -251,12 +253,12 @@ class MediaNotificationBackgroundBlackWhiteTest
};
INSTANTIATE_TEST_SUITE_P(All,
- MediaNotificationBackgroundBlackWhiteTest,
+ MediaNotificationBackgroundImplBlackWhiteTest,
testing::Values(SK_ColorBLACK, SK_ColorWHITE));
// If the most popular color is black or white but there is no secondary color
// we should use the most popular color.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveBackgroundColor_PopularBlackWhiteNoSecondaryColor) {
background()->UpdateArtwork(CreateTestBackgroundImage(GetParam()));
EXPECT_EQ(GetParam(), GetBackgroundColor());
@@ -264,7 +266,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If the most popular color is black or white and there is a secondary color
// that is very minor then we should use the most popular color.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveBackgroundColor_VeryPopularBlackWhite) {
background()->UpdateArtwork(
CreateTestBackgroundImage(GetParam(), SK_ColorYELLOW, 20));
@@ -273,7 +275,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If the most popular color is black or white but it is not that popular then
// we should use the secondary color.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveBackgroundColor_NotVeryPopularBlackWhite) {
constexpr SkColor kTestColor = SK_ColorYELLOW;
background()->UpdateArtwork(
@@ -283,7 +285,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If there are multiple vibrant colors then the foreground color should be the
// most popular one.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Palette_MultiVibrant) {
const SkColor kTestColor = SK_ColorCYAN;
@@ -297,7 +299,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If there is a vibrant and muted color then the foreground color should be the
// more vibrant one.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Palette_Vibrant) {
const SkColor kTestColor = GetColorFromSL(kVibrantSaturation, kNormalLuma);
@@ -311,7 +313,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If there are multiple muted colors then the foreground color should be the
// most popular one.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Palette_MultiMuted) {
const SkColor kTestColor = GetColorFromSL(kMutedSaturation, kNormalLuma);
@@ -325,7 +327,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If there is a normal and light muted color then the foreground color should
// be the normal one.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Palette_Muted) {
const SkColor kTestColor = GetColorFromSL(kMutedSaturation, kNormalLuma);
const SkColor kSecondColor =
@@ -340,7 +342,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If the best color is not the most popular one, but the most popular one is
// not that popular then we should use the best color.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Palette_NotPopular) {
const SkColor kTestColor = SK_ColorMAGENTA;
@@ -354,7 +356,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If we do not have a best color but we have a popular one over a threshold
// then we should use that one.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_MostPopular) {
const SkColor kTestColor = GetColorFromSL(kMutedSaturation, kNormalLuma);
@@ -367,7 +369,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If the background color is dark then we should select for a lighter color,
// otherwise we should select for a darker one.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Palette_MoreVibrant) {
const SkColor kTestColor =
GetColorFromSL(kVibrantSaturation, IsBlack() ? kLightLuma : kDarkLuma);
@@ -382,7 +384,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If the background color is dark then we should select for a lighter color,
// otherwise we should select for a darker one.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Palette_MoreMuted) {
const SkColor kTestColor =
GetColorFromSL(kMutedSaturation, IsBlack() ? kLightLuma : kDarkLuma);
@@ -398,7 +400,7 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
// If we do not have any colors then we should use the fallback color based on
// the background color.
-TEST_P(MediaNotificationBackgroundBlackWhiteTest,
+TEST_P(MediaNotificationBackgroundImplBlackWhiteTest,
DeriveForegroundColor_Fallback) {
background()->UpdateArtwork(CreateTestForegroundArtwork(
SK_ColorTRANSPARENT, SK_ColorTRANSPARENT, 0, 0));
@@ -408,10 +410,10 @@ TEST_P(MediaNotificationBackgroundBlackWhiteTest,
GetForegroundColor());
}
-// MediaNotificationBackgroundRTLTest will repeat these tests with RTL disabled
-// and enabled.
-class MediaNotificationBackgroundRTLTest
- : public MediaNotificationBackgroundTest,
+// MediaNotificationBackgroundImplRTLTest will repeat these tests with RTL
+// disabled and enabled.
+class MediaNotificationBackgroundImplRTLTest
+ : public MediaNotificationBackgroundImplTest,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
@@ -419,7 +421,7 @@ class MediaNotificationBackgroundRTLTest
switches::kForceUIDirection, GetParam() ? switches::kForceDirectionRTL
: switches::kForceDirectionLTR);
- MediaNotificationBackgroundTest::SetUp();
+ MediaNotificationBackgroundImplTest::SetUp();
ASSERT_EQ(IsRTL(), base::i18n::IsRTL());
}
@@ -432,10 +434,10 @@ class MediaNotificationBackgroundRTLTest
};
INSTANTIATE_TEST_SUITE_P(All,
- MediaNotificationBackgroundRTLTest,
+ MediaNotificationBackgroundImplRTLTest,
testing::Bool());
-TEST_P(MediaNotificationBackgroundRTLTest, BoundsSanityCheck) {
+TEST_P(MediaNotificationBackgroundImplRTLTest, BoundsSanityCheck) {
// The test notification will have a width of 200 and a height of 50.
gfx::Rect bounds(0, 0, 200, 50);
auto owner = std::make_unique<views::StaticSizedView>();
diff --git a/chromium/components/media_message_center/media_notification_view.h b/chromium/components/media_message_center/media_notification_view.h
index 96aa7421f67..996ad77a930 100644
--- a/chromium/components/media_message_center/media_notification_view.h
+++ b/chromium/components/media_message_center/media_notification_view.h
@@ -45,6 +45,7 @@ class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationView
// Sets the icon to be displayed in the notification's header section.
// |vector_icon| must outlive the MediaNotificationView.
virtual void UpdateWithVectorIcon(const gfx::VectorIcon& vector_icon) = 0;
+ virtual void UpdateDeviceSelectorAvailability(bool availability) = 0;
};
} // namespace media_message_center
diff --git a/chromium/components/media_message_center/media_notification_view_impl.cc b/chromium/components/media_message_center/media_notification_view_impl.cc
index f571def9c92..a03909f33e5 100644
--- a/chromium/components/media_message_center/media_notification_view_impl.cc
+++ b/chromium/components/media_message_center/media_notification_view_impl.cc
@@ -6,7 +6,8 @@
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
-#include "components/media_message_center/media_notification_background.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_constants.h"
#include "components/media_message_center/media_notification_container.h"
#include "components/media_message_center/media_notification_item.h"
@@ -81,6 +82,7 @@ const gfx::VectorIcon* GetVectorIconForMediaAction(MediaSessionAction action) {
case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
+ case MediaSessionAction::kSwitchAudioDevice:
NOTREACHED();
break;
}
@@ -109,7 +111,8 @@ MediaNotificationViewImpl::MediaNotificationViewImpl(
std::unique_ptr<views::View> header_row_controls_view,
const base::string16& default_app_name,
int notification_width,
- bool should_show_icon)
+ bool should_show_icon,
+ BackgroundStyle background_style)
: container_(container),
item_(std::move(item)),
default_app_name_(default_app_name),
@@ -264,9 +267,13 @@ MediaNotificationViewImpl::MediaNotificationViewImpl(
picture_in_picture_button_ =
button_row_->AddChildView(std::move(picture_in_picture_button));
- SetBackground(std::make_unique<MediaNotificationBackground>(
- message_center::kNotificationCornerRadius,
- message_center::kNotificationCornerRadius, kMediaImageMaxWidthPct));
+ if (background_style == BackgroundStyle::kAshStyle) {
+ SetBackground(std::make_unique<MediaNotificationBackgroundAshImpl>());
+ } else {
+ SetBackground(std::make_unique<MediaNotificationBackgroundImpl>(
+ message_center::kNotificationCornerRadius,
+ message_center::kNotificationCornerRadius, kMediaImageMaxWidthPct));
+ }
UpdateCornerRadius(message_center::kNotificationCornerRadius,
message_center::kNotificationCornerRadius);
@@ -469,6 +476,12 @@ void MediaNotificationViewImpl::UpdateWithVectorIcon(
kIconMediaNotificationHeaderInsets);
}
+void MediaNotificationViewImpl::UpdateDeviceSelectorAvailability(
+ bool availability) {
+ GetMediaNotificationBackground()->UpdateDeviceSelectorAvailability(
+ availability);
+}
+
void MediaNotificationViewImpl::OnThemeChanged() {
MediaNotificationView::OnThemeChanged();
UpdateForegroundColor();
diff --git a/chromium/components/media_message_center/media_notification_view_impl.h b/chromium/components/media_message_center/media_notification_view_impl.h
index 0ebcf0f238a..4c8eaeda792 100644
--- a/chromium/components/media_message_center/media_notification_view_impl.h
+++ b/chromium/components/media_message_center/media_notification_view_impl.h
@@ -52,13 +52,20 @@ class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationViewImpl
kMaxValue = kSource,
};
+ // Allow MediaNotificationViewImpl show different styled background.
+ enum class BackgroundStyle {
+ kDefault,
+ kAshStyle,
+ };
+
MediaNotificationViewImpl(
MediaNotificationContainer* container,
base::WeakPtr<MediaNotificationItem> item,
std::unique_ptr<views::View> header_row_controls_view,
const base::string16& default_app_name,
int notification_width,
- bool should_show_icon);
+ bool should_show_icon,
+ BackgroundStyle background_style = BackgroundStyle::kDefault);
~MediaNotificationViewImpl() override;
// views::View:
@@ -81,6 +88,8 @@ class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationViewImpl
void UpdateWithMediaArtwork(const gfx::ImageSkia& image) override;
void UpdateWithFavicon(const gfx::ImageSkia& icon) override;
void UpdateWithVectorIcon(const gfx::VectorIcon& vector_icon) override;
+ void UpdateDeviceSelectorAvailability(bool availability) override;
+
void OnThemeChanged() override;
const views::Label* title_label_for_testing() const { return title_label_; }
diff --git a/chromium/components/media_message_center/media_notification_view_impl_unittest.cc b/chromium/components/media_message_center/media_notification_view_impl_unittest.cc
index 6d11c7d55f9..1b9b81789bc 100644
--- a/chromium/components/media_message_center/media_notification_view_impl_unittest.cc
+++ b/chromium/components/media_message_center/media_notification_view_impl_unittest.cc
@@ -16,7 +16,7 @@
#include "base/test/task_environment.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
-#include "components/media_message_center/media_notification_background.h"
+#include "components/media_message_center/media_notification_background_impl.h"
#include "components/media_message_center/media_notification_constants.h"
#include "components/media_message_center/media_notification_container.h"
#include "components/media_message_center/media_notification_controller.h"
@@ -68,15 +68,16 @@ class MockMediaNotificationController : public MediaNotificationController {
~MockMediaNotificationController() = default;
// MediaNotificationController implementation.
- MOCK_METHOD1(ShowNotification, void(const std::string& id));
- MOCK_METHOD1(HideNotification, void(const std::string& id));
- MOCK_METHOD1(RemoveItem, void(const std::string& id));
+ MOCK_METHOD(void, ShowNotification, (const std::string& id));
+ MOCK_METHOD(void, HideNotification, (const std::string& id));
+ MOCK_METHOD(void, RemoveItem, (const std::string& id));
scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override {
return nullptr;
}
- MOCK_METHOD2(LogMediaSessionActionButtonPressed,
- void(const std::string& id,
- media_session::mojom::MediaSessionAction action));
+ MOCK_METHOD(void,
+ LogMediaSessionActionButtonPressed,
+ (const std::string& id,
+ media_session::mojom::MediaSessionAction action));
private:
DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationController);
@@ -247,7 +248,9 @@ class MediaNotificationViewImplTest : public views::ViewsTestBase {
MediaSessionNotificationItem* GetItem() const { return item_.get(); }
const gfx::ImageSkia& GetArtworkImage() const {
- return view()->GetMediaNotificationBackground()->artwork_;
+ return static_cast<MediaNotificationBackgroundImpl*>(
+ view()->GetMediaNotificationBackground())
+ ->artwork_;
}
const gfx::ImageSkia& GetAppIcon() const {
@@ -539,7 +542,7 @@ TEST_F(MAYBE_MediaNotificationViewImplTest, PlayToggle_FromObserver_Empty) {
views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
GetButtonForAction(MediaSessionAction::kPlay));
ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
- EXPECT_FALSE(button->toggled());
+ EXPECT_FALSE(button->GetToggled());
}
view()->UpdateWithMediaSessionInfo(
@@ -549,7 +552,7 @@ TEST_F(MAYBE_MediaNotificationViewImplTest, PlayToggle_FromObserver_Empty) {
views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
GetButtonForAction(MediaSessionAction::kPlay));
ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
- EXPECT_FALSE(button->toggled());
+ EXPECT_FALSE(button->GetToggled());
}
}
@@ -562,7 +565,7 @@ TEST_F(MAYBE_MediaNotificationViewImplTest,
views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
GetButtonForAction(MediaSessionAction::kPlay));
ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
- EXPECT_FALSE(button->toggled());
+ EXPECT_FALSE(button->GetToggled());
}
media_session::mojom::MediaSessionInfoPtr session_info(
@@ -576,7 +579,7 @@ TEST_F(MAYBE_MediaNotificationViewImplTest,
views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
GetButtonForAction(MediaSessionAction::kPause));
ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
- EXPECT_TRUE(button->toggled());
+ EXPECT_TRUE(button->GetToggled());
}
session_info->playback_state =
@@ -587,7 +590,7 @@ TEST_F(MAYBE_MediaNotificationViewImplTest,
views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
GetButtonForAction(MediaSessionAction::kPlay));
ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
- EXPECT_FALSE(button->toggled());
+ EXPECT_FALSE(button->GetToggled());
}
}
diff --git a/chromium/components/media_message_center/media_notification_view_modern_impl.cc b/chromium/components/media_message_center/media_notification_view_modern_impl.cc
new file mode 100644
index 00000000000..16fa2155197
--- /dev/null
+++ b/chromium/components/media_message_center/media_notification_view_modern_impl.cc
@@ -0,0 +1,595 @@
+// 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/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "components/media_message_center/media_notification_background_impl.h"
+#include "components/media_message_center/media_notification_constants.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/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/gfx/canvas.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/views/border.h"
+#include "ui/views/controls/button/image_button_factory.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, 175};
+constexpr gfx::Size kInfoContainerSize = {
+ kMediaNotificationViewBaseSize.width(), 100};
+constexpr gfx::Insets kArtworkContainerBorderInsets = {15, 10, 15, 0};
+constexpr gfx::Size kArtworkContainerSize = {100, kInfoContainerSize.height()};
+constexpr int kArtworkVignetteCornerRadius = 5;
+constexpr gfx::Size kLabelsContainerBaseSize = {
+ kMediaNotificationViewBaseSize.width() - kArtworkContainerSize.width(),
+ kInfoContainerSize.height()};
+constexpr gfx::Insets kLabelsContainerBorderInsets = {15, 10, 0, 0};
+constexpr gfx::Size kPipSeparatorSize = {1, 14};
+constexpr gfx::Size kPipButtonSize = {20, 20};
+constexpr int kPipButtonIconSize = 18;
+constexpr gfx::Size kNotificationControlsSpacerSize = {100, 1};
+constexpr gfx::Insets kNotificationControlsInsets = {5, 0, 0, 5};
+constexpr gfx::Size kMediaControlsContainerSize = {350, 75};
+constexpr int kMediaControlsButtonSpacing = 16;
+constexpr gfx::Insets kMediaControlsBorderInsets = {0, 0, 10, 0};
+
+constexpr int kTitleArtistLineHeight = 20;
+constexpr gfx::Size kMediaButtonSize = gfx::Size(36, 36);
+constexpr int kMediaButtonIconSize = 20;
+
+// An image view with a rounded rectangle vignette
+class MediaArtworkView : public views::ImageView {
+ public:
+ explicit MediaArtworkView(float corner_radius)
+ : corner_radius_(corner_radius) {}
+
+ void SetVignetteColor(const SkColor& vignette_color) {
+ vignette_color_ = vignette_color;
+ }
+
+ // ImageView
+ void OnPaint(gfx::Canvas* canvas) override;
+
+ private:
+ SkColor vignette_color_;
+ float corner_radius_;
+};
+
+void MediaArtworkView::OnPaint(gfx::Canvas* canvas) {
+ views::ImageView::OnPaint(canvas);
+ auto path = SkPath().addRoundRect(RectToSkRect(GetLocalBounds()),
+ corner_radius_, corner_radius_);
+ path.toggleInverseFillType();
+ cc::PaintFlags paint_flags;
+ paint_flags.setStyle(cc::PaintFlags::kFill_Style);
+ paint_flags.setAntiAlias(true);
+ paint_flags.setColor(vignette_color_);
+ canvas->DrawPath(path, paint_flags);
+}
+
+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:
+ NOTREACHED();
+ break;
+ }
+
+ return nullptr;
+}
+
+} // 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,
+ int notification_width)
+ : container_(container), item_(std::move(item)) {
+ DCHECK(container_);
+
+ SetPreferredSize(kMediaNotificationViewBaseSize);
+
+ DCHECK(notification_width >= kMediaNotificationViewBaseSize.width())
+ << "MediaNotificationViewModernImpl expects a width of at least "
+ << kMediaNotificationViewBaseSize.width();
+ auto border_insets = gfx::Insets(
+ 0, (kMediaNotificationViewBaseSize.width() - notification_width) / 2);
+ SetBorder(views::CreateEmptyBorder(border_insets));
+
+ SetLayoutManager(std::make_unique<views::BoxLayout>(
+ views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0));
+
+ SetBackground(std::make_unique<MediaNotificationBackgroundImpl>(
+ message_center::kNotificationCornerRadius,
+ message_center::kNotificationCornerRadius, 0));
+
+ UpdateCornerRadius(message_center::kNotificationCornerRadius,
+ message_center::kNotificationCornerRadius);
+
+ {
+ // 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, gfx::Insets(), 0));
+ info_container_layout->set_cross_axis_alignment(
+ views::BoxLayout::CrossAxisAlignment::kStart);
+
+ {
+ auto artwork_container = std::make_unique<views::View>();
+ artwork_container->SetBorder(
+ views::CreateEmptyBorder(kArtworkContainerBorderInsets));
+ artwork_container->SetPreferredSize(kArtworkContainerSize);
+
+ // The artwork container will become visible once artwork has been set in
+ // UpdateWithMediaArtwork
+ artwork_container->SetVisible(false);
+
+ 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);
+ 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(
+ {kLabelsContainerBaseSize.width() -
+ (notification_controls_view->GetPreferredSize().width() +
+ kNotificationControlsInsets.width()),
+ kLabelsContainerBaseSize.height()});
+ labels_container->SetBorder(
+ views::CreateEmptyBorder(kLabelsContainerBorderInsets));
+
+ 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::kStart);
+ 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));
+ }
+
+ {
+ // Put a vertical spacer between the labels and the pip button.
+ auto spacer = std::make_unique<views::View>();
+ spacer->SetPreferredSize(kPipSeparatorSize);
+ labels_container->AddChildView(std::move(spacer));
+ }
+
+ {
+ // The picture-in-picture button appears directly under the media
+ // labels.
+ auto picture_in_picture_button =
+ views::CreateVectorToggleImageButton(this);
+ picture_in_picture_button->set_tag(
+ static_cast<int>(MediaSessionAction::kEnterPictureInPicture));
+ picture_in_picture_button->SetPreferredSize(kPipButtonSize);
+ 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->EnableCanvasFlippingForRTLUI(false);
+ views::SetImageFromVectorIconWithColor(
+ picture_in_picture_button.get(),
+ *GetVectorIconForMediaAction(
+ MediaSessionAction::kEnterPictureInPicture),
+ kPipButtonIconSize, SK_ColorBLACK);
+ picture_in_picture_button_ = labels_container->AddChildView(
+ std::move(picture_in_picture_button));
+ }
+
+ info_container->AddChildView(std::move(labels_container));
+ }
+
+ {
+ // If there is no artwork to display, a vertical spacer should be added
+ // between the labels container and the dismiss button.
+ auto notification_controls_spacer = std::make_unique<views::View>();
+ notification_controls_spacer->SetPreferredSize(
+ kNotificationControlsSpacerSize);
+ notification_controls_spacer_ =
+ info_container->AddChildView(std::move(notification_controls_spacer));
+ }
+
+ notification_controls_view->SetProperty(views::kMarginsKey,
+ kNotificationControlsInsets);
+ info_container->AddChildView(std::move(notification_controls_view));
+
+ AddChildView(std::move(info_container));
+ }
+
+ {
+ // 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, gfx::Insets(),
+ 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_container->SetBorder(
+ views::CreateEmptyBorder(kMediaControlsBorderInsets));
+
+ // 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,
+ l10n_util::GetStringUTF16(
+ IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PREVIOUS_TRACK));
+ CreateMediaButton(
+ media_controls_container.get(), MediaSessionAction::kSeekBackward,
+ l10n_util::GetStringUTF16(
+ IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_BACKWARD));
+
+ {
+ auto play_pause_button = views::CreateVectorToggleImageButton(this);
+ play_pause_button->set_tag(static_cast<int>(MediaSessionAction::kPlay));
+ play_pause_button->SetPreferredSize(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->EnableCanvasFlippingForRTLUI(false);
+ play_pause_button_ =
+ media_controls_container->AddChildView(std::move(play_pause_button));
+ }
+
+ CreateMediaButton(
+ media_controls_container.get(), MediaSessionAction::kSeekForward,
+ l10n_util::GetStringUTF16(
+ IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_SEEK_FORWARD));
+ CreateMediaButton(
+ media_controls_container.get(), MediaSessionAction::kNextTrack,
+ l10n_util::GetStringUTF16(
+ IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_NEXT_TRACK));
+
+ media_controls_container_ =
+ AddChildView(std::move(media_controls_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::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ if (item_) {
+ item_->OnMediaSessionActionButtonPressed(GetActionFromButtonTag(*sender));
+ }
+}
+
+void MediaNotificationViewModernImpl::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 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::UpdateWithMediaArtwork(
+ const gfx::ImageSkia& image) {
+ GetMediaNotificationBackground()->UpdateArtwork(image);
+
+ UMA_HISTOGRAM_BOOLEAN(kArtworkHistogramName, !image.isNull());
+
+ if (!image.isNull()) {
+ artwork_container_->SetVisible(true);
+ // When there is artwork to display, this spacer is no logner needed
+ notification_controls_spacer_->SetVisible(false);
+ }
+
+ artwork_->SetImage(image);
+ artwork_->SetPreferredSize({70, 70});
+ artwork_->SetVignetteColor(
+ GetMediaNotificationBackground()->GetBackgroundColor(*this));
+
+ UpdateForegroundColor();
+
+ container_->OnMediaArtworkChanged(image);
+
+ PreferredSizeChanged();
+ Layout();
+ SchedulePaint();
+}
+
+void MediaNotificationViewModernImpl::UpdateWithFavicon(
+ const gfx::ImageSkia& icon) {
+ GetMediaNotificationBackground()->UpdateFavicon(icon);
+
+ UpdateForegroundColor();
+ SchedulePaint();
+}
+
+void MediaNotificationViewModernImpl::OnThemeChanged() {
+ MediaNotificationView::OnThemeChanged();
+ UpdateForegroundColor();
+}
+
+void MediaNotificationViewModernImpl::UpdateDeviceSelectorAvailability(
+ bool availability) {
+ GetMediaNotificationBackground()->UpdateDeviceSelectorAvailability(
+ availability);
+}
+
+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();
+ }
+
+ container_->OnVisibleActionsChanged(enabled_actions_);
+}
+
+void MediaNotificationViewModernImpl::CreateMediaButton(
+ views::View* parent_view,
+ MediaSessionAction action,
+ const base::string16& accessible_name) {
+ auto button = views::CreateVectorImageButton(this);
+ button->set_tag(static_cast<int>(action));
+ button->SetPreferredSize(kMediaButtonSize);
+ button->SetAccessibleName(accessible_name);
+ button->SetTooltipText(accessible_name);
+ button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
+ button->EnableCanvasFlippingForRTLUI(false);
+ parent_view->AddChildView(std::move(button));
+}
+
+MediaNotificationBackground*
+MediaNotificationViewModernImpl::GetMediaNotificationBackground() {
+ return static_cast<MediaNotificationBackground*>(background());
+}
+
+void MediaNotificationViewModernImpl::UpdateForegroundColor() {
+ const SkColor background =
+ GetMediaNotificationBackground()->GetBackgroundColor(*this);
+ const SkColor foreground =
+ GetMediaNotificationBackground()->GetForegroundColor(*this);
+ const SkColor disabled_icon_color =
+ SkColorSetA(foreground, gfx::kDisabledControlAlpha);
+
+ // Update the colors for the labels
+ title_label_->SetEnabledColor(foreground);
+ subtitle_label_->SetEnabledColor(disabled_icon_color);
+
+ title_label_->SetBackgroundColor(background);
+ subtitle_label_->SetBackgroundColor(background);
+
+ // Update the colors for the toggle buttons (play/pause and
+ // picture-in-picture)
+ views::SetImageFromVectorIconWithColor(
+ play_pause_button_,
+ *GetVectorIconForMediaAction(MediaSessionAction::kPlay),
+ kMediaButtonIconSize, foreground);
+ views::SetToggledImageFromVectorIconWithColor(
+ play_pause_button_,
+ *GetVectorIconForMediaAction(MediaSessionAction::kPause),
+ kMediaButtonIconSize, foreground, disabled_icon_color);
+
+ views::SetImageFromVectorIconWithColor(
+ picture_in_picture_button_,
+ *GetVectorIconForMediaAction(MediaSessionAction::kEnterPictureInPicture),
+ kPipButtonIconSize, foreground);
+ views::SetToggledImageFromVectorIconWithColor(
+ picture_in_picture_button_,
+ *GetVectorIconForMediaAction(MediaSessionAction::kExitPictureInPicture),
+ kPipButtonIconSize, foreground, 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;
+
+ views::ImageButton* button = static_cast<views::ImageButton*>(child);
+
+ views::SetImageFromVectorIconWithColor(
+ button, *GetVectorIconForMediaAction(GetActionFromButtonTag(*button)),
+ kMediaButtonIconSize, foreground);
+
+ button->SchedulePaint();
+ }
+
+ SchedulePaint();
+ container_->OnColorsChanged(foreground, background);
+}
+
+} // namespace media_message_center
diff --git a/chromium/components/media_message_center/media_notification_view_modern_impl.h b/chromium/components/media_message_center/media_notification_view_modern_impl.h
new file mode 100644
index 00000000000..96b0291da44
--- /dev/null
+++ b/chromium/components/media_message_center/media_notification_view_modern_impl.h
@@ -0,0 +1,154 @@
+// 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.
+
+#ifndef COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_VIEW_MODERN_IMPL_H_
+#define COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_VIEW_MODERN_IMPL_H_
+
+#include "components/media_message_center/media_notification_view.h"
+
+#include "base/component_export.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "components/media_message_center/media_notification_view.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+
+namespace views {
+class ToggleImageButton;
+} // namespace views
+
+namespace media_message_center {
+
+namespace {
+class MediaArtworkView;
+} // anonymous namespace
+
+class MediaNotificationBackground;
+class MediaNotificationContainer;
+class MediaNotificationItem;
+
+class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationViewModernImpl
+ : public MediaNotificationView,
+ public views::ButtonListener {
+ public:
+ // The name of the histogram used when recording whether the artwork was
+ // present.
+ static const char kArtworkHistogramName[];
+
+ // The name of the histogram used when recording the type of metadata that was
+ // displayed.
+ static const char kMetadataHistogramName[];
+
+ // The type of metadata that was displayed. This is used in metrics so new
+ // values must only be added to the end.
+ enum class Metadata {
+ kTitle,
+ kArtist,
+ kAlbum,
+ kCount,
+ kSource,
+ kMaxValue = kSource,
+ };
+
+ MediaNotificationViewModernImpl(
+ MediaNotificationContainer* container,
+ base::WeakPtr<MediaNotificationItem> item,
+ std::unique_ptr<views::View> notification_controls_view,
+ int notification_width);
+ MediaNotificationViewModernImpl(const MediaNotificationViewModernImpl&) =
+ delete;
+ MediaNotificationViewModernImpl& operator=(
+ const MediaNotificationViewModernImpl&) = delete;
+ ~MediaNotificationViewModernImpl() override;
+
+ // views::View:
+ void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+ void OnThemeChanged() override;
+
+ // views::ButtonListener:
+ void ButtonPressed(views::Button* sender, const ui::Event& event) override;
+
+ // MediaNotificationView
+ void SetForcedExpandedState(bool* forced_expanded_state) override {}
+ void SetExpanded(bool expanded) override {}
+ void UpdateCornerRadius(int top_radius, int bottom_radius) override;
+ void UpdateWithMediaSessionInfo(
+ const media_session::mojom::MediaSessionInfoPtr& session_info) override;
+ void UpdateWithMediaMetadata(
+ const media_session::MediaMetadata& metadata) override;
+ void UpdateWithMediaActions(
+ const base::flat_set<media_session::mojom::MediaSessionAction>& actions)
+ override;
+ void UpdateWithMediaArtwork(const gfx::ImageSkia& image) override;
+ void UpdateWithFavicon(const gfx::ImageSkia& icon) override;
+ void UpdateWithVectorIcon(const gfx::VectorIcon& vector_icon) override {}
+ void UpdateDeviceSelectorAvailability(bool availability) override;
+
+ // Testing methods
+ const views::Label* title_label_for_testing() const { return title_label_; }
+
+ const views::Label* subtitle_label_for_testing() const {
+ return subtitle_label_;
+ }
+
+ const views::Button* picture_in_picture_button_for_testing() const {
+ return picture_in_picture_button_;
+ }
+
+ const views::View* media_controls_container_for_testing() const {
+ return media_controls_container_;
+ }
+
+ private:
+ friend class MediaNotificationViewModernImplTest;
+
+ // Creates an image button with an icon that matches |action| and adds it
+ // to |parent_view|. When clicked it will trigger |action| on the session.
+ // |accessible_name| is the text used for screen readers and the
+ // button's tooltip.
+ void CreateMediaButton(views::View* parent_view,
+ media_session::mojom::MediaSessionAction action,
+ const base::string16& accessible_name);
+
+ void UpdateActionButtonsVisibility();
+
+ MediaNotificationBackground* GetMediaNotificationBackground();
+
+ void UpdateForegroundColor();
+
+ // Container that receives events.
+ MediaNotificationContainer* const container_;
+
+ // Keeps track of media metadata and controls the session when buttons are
+ // clicked.
+ base::WeakPtr<MediaNotificationItem> item_;
+
+ bool has_artwork_ = false;
+
+ // Set of enabled actions.
+ base::flat_set<media_session::mojom::MediaSessionAction> enabled_actions_;
+
+ // Stores the text to be read by screen readers describing the notification.
+ // Contains the title, artist and album separated by hyphens.
+ base::string16 accessible_name_;
+
+ MediaNotificationBackground* background_;
+
+ // Container views directly attached to this view.
+ views::View* artwork_container_ = nullptr;
+ MediaArtworkView* artwork_ = nullptr;
+ views::Label* title_label_ = nullptr;
+ views::Label* subtitle_label_ = nullptr;
+ views::ToggleImageButton* picture_in_picture_button_ = nullptr;
+ views::View* notification_controls_spacer_ = nullptr;
+ views::View* media_controls_container_ = nullptr;
+ views::ToggleImageButton* play_pause_button_ = nullptr;
+};
+
+} // namespace media_message_center
+
+#endif // COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_VIEW_MODERN_IMPL_H_
diff --git a/chromium/components/media_message_center/media_notification_view_modern_impl_unittest.cc b/chromium/components/media_message_center/media_notification_view_modern_impl_unittest.cc
new file mode 100644
index 00000000000..6fa81fba794
--- /dev/null
+++ b/chromium/components/media_message_center/media_notification_view_modern_impl_unittest.cc
@@ -0,0 +1,1074 @@
+// 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 <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
+#include "components/media_message_center/media_notification_background_impl.h"
+#include "components/media_message_center/media_notification_constants.h"
+#include "components/media_message_center/media_notification_container.h"
+#include "components/media_message_center/media_notification_controller.h"
+#include "components/media_message_center/media_notification_util.h"
+#include "components/media_message_center/media_session_notification_item.h"
+#include "services/media_session/public/cpp/test/test_media_controller.h"
+#include "services/media_session/public/mojom/audio_focus.mojom.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/public/cpp/message_center_constants.h"
+#include "ui/message_center/views/message_view_factory.h"
+#include "ui/message_center/views/notification_control_buttons_view.h"
+#include "ui/message_center/views/notification_header_view.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/test/views_test_base.h"
+
+namespace media_message_center {
+
+using media_session::mojom::MediaSessionAction;
+using media_session::test::TestMediaController;
+using testing::_;
+using testing::Expectation;
+using testing::Invoke;
+
+namespace {
+
+const int kMediaButtonIconSize = 20;
+const int kPipButtonIconSize = 18;
+
+const gfx::Size kWidgetSize(500, 500);
+
+constexpr int kViewWidth = 350;
+const gfx::Size kViewSize(kViewWidth, 400);
+
+class MockMediaNotificationController : public MediaNotificationController {
+ public:
+ MockMediaNotificationController() = default;
+ ~MockMediaNotificationController() override = default;
+
+ // MediaNotificationController implementation.
+ MOCK_METHOD1(ShowNotification, void(const std::string& id));
+ MOCK_METHOD1(HideNotification, void(const std::string& id));
+ MOCK_METHOD1(RemoveItem, void(const std::string& id));
+ scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override {
+ return nullptr;
+ }
+ MOCK_METHOD2(LogMediaSessionActionButtonPressed,
+ void(const std::string& id,
+ media_session::mojom::MediaSessionAction action));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationController);
+};
+
+class MockMediaNotificationContainer : public MediaNotificationContainer {
+ public:
+ MockMediaNotificationContainer() = default;
+ ~MockMediaNotificationContainer() override = default;
+
+ // MediaNotificationContainer implementation.
+ MOCK_METHOD1(OnExpanded, void(bool expanded));
+ MOCK_METHOD1(
+ OnMediaSessionInfoChanged,
+ void(const media_session::mojom::MediaSessionInfoPtr& session_info));
+ MOCK_METHOD1(OnMediaSessionMetadataChanged,
+ void(const media_session::MediaMetadata& metadata));
+ MOCK_METHOD1(OnVisibleActionsChanged,
+ void(const base::flat_set<MediaSessionAction>& actions));
+ MOCK_METHOD1(OnMediaArtworkChanged, void(const gfx::ImageSkia& image));
+ MOCK_METHOD2(OnColorsChanged, void(SkColor foreground, SkColor background));
+ MOCK_METHOD0(OnHeaderClicked, void());
+
+ MediaNotificationViewModernImpl* view() const { return view_; }
+ void SetView(MediaNotificationViewModernImpl* view) { view_ = view; }
+
+ private:
+ MediaNotificationViewModernImpl* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationContainer);
+};
+
+} // namespace
+
+class MediaNotificationViewModernImplTest : public views::ViewsTestBase {
+ public:
+ MediaNotificationViewModernImplTest()
+ : views::ViewsTestBase(
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+ ~MediaNotificationViewModernImplTest() override = default;
+
+ void SetUp() override {
+ views::ViewsTestBase::SetUp();
+
+ request_id_ = base::UnguessableToken::Create();
+
+ // Create a new MediaNotificationViewModernImpl whenever the
+ // MediaSessionNotificationItem says to show the notification.
+ EXPECT_CALL(controller_, ShowNotification(request_id_.ToString()))
+ .WillRepeatedly(InvokeWithoutArgs(
+ this, &MediaNotificationViewModernImplTest::CreateView));
+
+ // Create a widget to show on the screen for testing screen coordinates and
+ // focus.
+ widget_ = std::make_unique<views::Widget>();
+ views::Widget::InitParams params =
+ CreateParams(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.bounds = gfx::Rect(kWidgetSize);
+ widget_->Init(std::move(params));
+ widget_->Show();
+
+ CreateViewFromMediaSessionInfo(
+ media_session::mojom::MediaSessionInfo::New());
+ }
+
+ void CreateViewFromMediaSessionInfo(
+ media_session::mojom::MediaSessionInfoPtr session_info) {
+ session_info->is_controllable = true;
+ mojo::Remote<media_session::mojom::MediaController> controller;
+ item_ = std::make_unique<MediaSessionNotificationItem>(
+ &controller_, request_id_.ToString(), std::string(),
+ std::move(controller), std::move(session_info));
+
+ // Update the metadata.
+ media_session::MediaMetadata metadata;
+ metadata.title = base::ASCIIToUTF16("title");
+ metadata.artist = base::ASCIIToUTF16("artist");
+ metadata.source_title = base::ASCIIToUTF16("source title");
+ item_->MediaSessionMetadataChanged(metadata);
+
+ // Inject the test media controller into the item.
+ media_controller_ = std::make_unique<TestMediaController>();
+ item_->SetMediaControllerForTesting(
+ media_controller_->CreateMediaControllerRemote());
+ }
+
+ void TearDown() override {
+ container_.SetView(nullptr);
+ widget_.reset();
+
+ actions_.clear();
+
+ views::ViewsTestBase::TearDown();
+ }
+
+ void EnableAllActions() {
+ actions_.insert(MediaSessionAction::kPlay);
+ actions_.insert(MediaSessionAction::kPause);
+ actions_.insert(MediaSessionAction::kPreviousTrack);
+ actions_.insert(MediaSessionAction::kNextTrack);
+ actions_.insert(MediaSessionAction::kSeekBackward);
+ actions_.insert(MediaSessionAction::kSeekForward);
+ actions_.insert(MediaSessionAction::kStop);
+ actions_.insert(MediaSessionAction::kEnterPictureInPicture);
+ actions_.insert(MediaSessionAction::kExitPictureInPicture);
+
+ NotifyUpdatedActions();
+ }
+
+ void EnableAction(MediaSessionAction action) {
+ actions_.insert(action);
+ NotifyUpdatedActions();
+ }
+
+ void DisableAction(MediaSessionAction action) {
+ actions_.erase(action);
+ NotifyUpdatedActions();
+ }
+
+ MockMediaNotificationContainer& container() { return container_; }
+
+ MockMediaNotificationController& controller() { return controller_; }
+
+ MediaNotificationViewModernImpl* view() const { return container_.view(); }
+
+ TestMediaController* media_controller() const {
+ return media_controller_.get();
+ }
+
+ const base::string16& accessible_name() const {
+ return view()->accessible_name_;
+ }
+
+ views::Label* title_label() const { return view()->title_label_; }
+
+ views::Label* subtitle_label() const { return view()->subtitle_label_; }
+
+ views::View* artwork_container() const { return view()->artwork_container_; }
+
+ views::View* media_controls_container() const {
+ return view()->media_controls_container_;
+ }
+
+ std::vector<views::Button*> media_control_buttons() const {
+ std::vector<views::Button*> buttons;
+ auto children = view()->media_controls_container_->children();
+ std::transform(
+ children.begin(), children.end(), std::back_inserter(buttons),
+ [](views::View* child) { return views::Button::AsButton(child); });
+ buttons.push_back(
+ views::Button::AsButton(view()->picture_in_picture_button_));
+ return buttons;
+ }
+
+ views::Button* picture_in_picture_button() const {
+ return view()->picture_in_picture_button_;
+ }
+
+ views::Button* GetButtonForAction(MediaSessionAction action) const {
+ auto buttons = media_control_buttons();
+ const auto i = std::find_if(
+ buttons.begin(), buttons.end(), [action](const views::Button* button) {
+ return button->tag() == static_cast<int>(action);
+ });
+ return (i == buttons.end()) ? nullptr : *i;
+ }
+
+ bool IsActionButtonVisible(MediaSessionAction action) const {
+ return GetButtonForAction(action)->GetVisible();
+ }
+
+ MediaSessionNotificationItem* GetItem() const { return item_.get(); }
+
+ const gfx::ImageSkia& GetArtworkImage() const {
+ return static_cast<MediaNotificationBackgroundImpl*>(
+ view()->GetMediaNotificationBackground())
+ ->artwork_;
+ }
+
+ void SimulateButtonClick(MediaSessionAction action) {
+ views::Button* button = GetButtonForAction(action);
+ EXPECT_TRUE(button->GetVisible());
+
+ view()->ButtonPressed(
+ button, ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+ ui::EventTimeForNow(), 0, 0));
+ }
+
+ void SimulateTab() {
+ ui::KeyEvent pressed_tab(ui::ET_KEY_PRESSED, ui::VKEY_TAB, ui::EF_NONE);
+ view()->GetFocusManager()->OnKeyEvent(pressed_tab);
+ }
+
+ void ExpectHistogramActionRecorded(MediaSessionAction action) {
+ histogram_tester_.ExpectUniqueSample(
+ MediaSessionNotificationItem::kUserActionHistogramName,
+ static_cast<base::HistogramBase::Sample>(action), 1);
+ }
+
+ void ExpectHistogramArtworkRecorded(bool present, int count) {
+ histogram_tester_.ExpectBucketCount(
+ MediaNotificationViewModernImpl::kArtworkHistogramName,
+ static_cast<base::HistogramBase::Sample>(present), count);
+ }
+
+ void ExpectHistogramMetadataRecorded(
+ MediaNotificationViewModernImpl::Metadata metadata,
+ int count) {
+ histogram_tester_.ExpectBucketCount(
+ MediaNotificationViewModernImpl::kMetadataHistogramName,
+ static_cast<base::HistogramBase::Sample>(metadata), count);
+ }
+
+ void AdvanceClockMilliseconds(int milliseconds) {
+ task_environment()->FastForwardBy(
+ base::TimeDelta::FromMilliseconds(milliseconds));
+ }
+
+ private:
+ void NotifyUpdatedActions() {
+ item_->MediaSessionActionsChanged(
+ std::vector<MediaSessionAction>(actions_.begin(), actions_.end()));
+ }
+
+ void CreateView() {
+ // Create a MediaNotificationViewModernImpl.
+ auto view = std::make_unique<MediaNotificationViewModernImpl>(
+ &container_, item_->GetWeakPtr(), std::make_unique<views::View>(),
+ kViewWidth);
+ view->SetSize(kViewSize);
+
+ // Display it in |widget_|. Widget now owns |view|.
+ // And associate it with |container_|.
+ container_.SetView(widget_->SetContentsView(std::move(view)));
+ }
+
+ base::UnguessableToken request_id_;
+
+ base::HistogramTester histogram_tester_;
+
+ base::flat_set<MediaSessionAction> actions_;
+
+ std::unique_ptr<TestMediaController> media_controller_;
+ MockMediaNotificationContainer container_;
+ MockMediaNotificationController controller_;
+ std::unique_ptr<MediaSessionNotificationItem> item_;
+ std::unique_ptr<views::Widget> widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaNotificationViewModernImplTest);
+};
+
+// TODO(crbug.com/1009287): many of these tests are failing on TSan builds.
+#if defined(THREAD_SANITIZER)
+#define MAYBE_MediaNotificationViewModernImplTest \
+ DISABLED_MediaNotificationViewModernImplTest
+class DISABLED_MediaNotificationViewModernImplTest
+ : public MediaNotificationViewModernImplTest {};
+#else
+#define MAYBE_MediaNotificationViewModernImplTest \
+ MediaNotificationViewModernImplTest
+#endif
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, ButtonsSanityCheck) {
+ EnableAllActions();
+
+ EXPECT_TRUE(media_controls_container()->GetVisible());
+ EXPECT_GT(media_controls_container()->width(), 0);
+ EXPECT_GT(media_controls_container()->height(), 0);
+
+ auto buttons = media_control_buttons();
+ EXPECT_EQ(6u, buttons.size());
+
+ for (auto* button : buttons) {
+ EXPECT_TRUE(button->GetVisible());
+ if (button == picture_in_picture_button()) {
+ EXPECT_LT(kPipButtonIconSize, button->width());
+ EXPECT_LT(kPipButtonIconSize, button->height());
+ } else {
+ EXPECT_LT(kMediaButtonIconSize, button->width());
+ EXPECT_LT(kMediaButtonIconSize, button->height());
+ }
+ EXPECT_FALSE(views::Button::AsButton(button)->GetAccessibleName().empty());
+ }
+
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPreviousTrack));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kNextTrack));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kSeekBackward));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kSeekForward));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kEnterPictureInPicture));
+
+ // |kPause| cannot be present if |kPlay| is.
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kExitPictureInPicture));
+}
+
+#if defined(OS_WIN)
+#define MAYBE_ButtonsFocusCheck DISABLED_ButtonsFocusCheck
+#else
+#define MAYBE_ButtonsFocusCheck ButtonsFocusCheck
+#endif
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, MAYBE_ButtonsFocusCheck) {
+ // Expand and enable all actions to show all buttons.
+ EnableAllActions();
+
+ views::FocusManager* focus_manager = view()->GetFocusManager();
+
+ {
+ // Focus the first action button.
+ auto* button = GetButtonForAction(MediaSessionAction::kPreviousTrack);
+ focus_manager->SetFocusedView(button);
+ EXPECT_EQ(button, focus_manager->GetFocusedView());
+ }
+
+ SimulateTab();
+ EXPECT_EQ(GetButtonForAction(MediaSessionAction::kSeekBackward),
+ focus_manager->GetFocusedView());
+
+ SimulateTab();
+ EXPECT_EQ(GetButtonForAction(MediaSessionAction::kPlay),
+ focus_manager->GetFocusedView());
+
+ SimulateTab();
+ EXPECT_EQ(GetButtonForAction(MediaSessionAction::kSeekForward),
+ focus_manager->GetFocusedView());
+
+ SimulateTab();
+ EXPECT_EQ(GetButtonForAction(MediaSessionAction::kNextTrack),
+ focus_manager->GetFocusedView());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, PlayPauseButtonTooltipCheck) {
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+
+ EXPECT_CALL(container(), OnMediaSessionInfoChanged(_));
+
+ auto* button = GetButtonForAction(MediaSessionAction::kPlay);
+ base::string16 tooltip = button->GetTooltipText(gfx::Point());
+ EXPECT_FALSE(tooltip.empty());
+
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ session_info->is_controllable = true;
+ GetItem()->MediaSessionInfoChanged(session_info.Clone());
+
+ base::string16 new_tooltip = button->GetTooltipText(gfx::Point());
+ EXPECT_FALSE(new_tooltip.empty());
+ EXPECT_NE(tooltip, new_tooltip);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, NextTrackButtonClick) {
+ EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+ _, MediaSessionAction::kNextTrack));
+ EnableAction(MediaSessionAction::kNextTrack);
+
+ EXPECT_EQ(0, media_controller()->next_track_count());
+
+ SimulateButtonClick(MediaSessionAction::kNextTrack);
+ GetItem()->FlushForTesting();
+
+ EXPECT_EQ(1, media_controller()->next_track_count());
+ ExpectHistogramActionRecorded(MediaSessionAction::kNextTrack);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, PlayButtonClick) {
+ EXPECT_CALL(controller(),
+ LogMediaSessionActionButtonPressed(_, MediaSessionAction::kPlay));
+ EnableAction(MediaSessionAction::kPlay);
+
+ EXPECT_EQ(0, media_controller()->resume_count());
+
+ SimulateButtonClick(MediaSessionAction::kPlay);
+ GetItem()->FlushForTesting();
+
+ EXPECT_EQ(1, media_controller()->resume_count());
+ ExpectHistogramActionRecorded(MediaSessionAction::kPlay);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, PauseButtonClick) {
+ EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+ _, MediaSessionAction::kPause));
+ EnableAction(MediaSessionAction::kPause);
+ EXPECT_CALL(container(), OnMediaSessionInfoChanged(_));
+
+ EXPECT_EQ(0, media_controller()->suspend_count());
+
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ session_info->is_controllable = true;
+ GetItem()->MediaSessionInfoChanged(session_info.Clone());
+
+ SimulateButtonClick(MediaSessionAction::kPause);
+ GetItem()->FlushForTesting();
+
+ EXPECT_EQ(1, media_controller()->suspend_count());
+ ExpectHistogramActionRecorded(MediaSessionAction::kPause);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, PreviousTrackButtonClick) {
+ EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+ _, MediaSessionAction::kPreviousTrack));
+ EnableAction(MediaSessionAction::kPreviousTrack);
+
+ EXPECT_EQ(0, media_controller()->previous_track_count());
+
+ SimulateButtonClick(MediaSessionAction::kPreviousTrack);
+ GetItem()->FlushForTesting();
+
+ EXPECT_EQ(1, media_controller()->previous_track_count());
+ ExpectHistogramActionRecorded(MediaSessionAction::kPreviousTrack);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, SeekBackwardButtonClick) {
+ EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+ _, MediaSessionAction::kSeekBackward));
+ EnableAction(MediaSessionAction::kSeekBackward);
+
+ EXPECT_EQ(0, media_controller()->seek_backward_count());
+
+ SimulateButtonClick(MediaSessionAction::kSeekBackward);
+ GetItem()->FlushForTesting();
+
+ EXPECT_EQ(1, media_controller()->seek_backward_count());
+ ExpectHistogramActionRecorded(MediaSessionAction::kSeekBackward);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, SeekForwardButtonClick) {
+ EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+ _, MediaSessionAction::kSeekForward));
+ EnableAction(MediaSessionAction::kSeekForward);
+
+ EXPECT_EQ(0, media_controller()->seek_forward_count());
+
+ SimulateButtonClick(MediaSessionAction::kSeekForward);
+ GetItem()->FlushForTesting();
+
+ EXPECT_EQ(1, media_controller()->seek_forward_count());
+ ExpectHistogramActionRecorded(MediaSessionAction::kSeekForward);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ PlayToggle_FromObserver_Empty) {
+ EnableAction(MediaSessionAction::kPlay);
+
+ {
+ views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
+ GetButtonForAction(MediaSessionAction::kPlay));
+ ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
+ EXPECT_FALSE(button->GetToggled());
+ }
+
+ view()->UpdateWithMediaSessionInfo(
+ media_session::mojom::MediaSessionInfo::New());
+
+ {
+ views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
+ GetButtonForAction(MediaSessionAction::kPlay));
+ ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
+ EXPECT_FALSE(button->GetToggled());
+ }
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ PlayToggle_FromObserver_PlaybackState) {
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+
+ {
+ views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
+ GetButtonForAction(MediaSessionAction::kPlay));
+ ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
+ EXPECT_FALSE(button->GetToggled());
+ }
+
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ view()->UpdateWithMediaSessionInfo(session_info.Clone());
+
+ {
+ views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
+ GetButtonForAction(MediaSessionAction::kPause));
+ ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
+ EXPECT_TRUE(button->GetToggled());
+ }
+
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPaused;
+ view()->UpdateWithMediaSessionInfo(session_info.Clone());
+
+ {
+ views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
+ GetButtonForAction(MediaSessionAction::kPlay));
+ ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
+ EXPECT_FALSE(button->GetToggled());
+ }
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, MetadataIsDisplayed) {
+ EnableAllActions();
+
+ EXPECT_TRUE(title_label()->GetVisible());
+ EXPECT_TRUE(subtitle_label()->GetVisible());
+
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, UpdateMetadata_FromObserver) {
+ EnableAllActions();
+
+ ExpectHistogramMetadataRecorded(
+ MediaNotificationViewModernImpl::Metadata::kTitle, 1);
+ ExpectHistogramMetadataRecorded(
+ MediaNotificationViewModernImpl::Metadata::kSource, 1);
+ ExpectHistogramMetadataRecorded(
+ MediaNotificationViewModernImpl::Metadata::kCount, 1);
+
+ media_session::MediaMetadata metadata;
+ metadata.title = base::ASCIIToUTF16("title2");
+ metadata.source_title = base::ASCIIToUTF16("source title2");
+ metadata.artist = base::ASCIIToUTF16("artist2");
+ metadata.album = base::ASCIIToUTF16("album");
+
+ EXPECT_CALL(container(), OnMediaSessionMetadataChanged(_));
+ GetItem()->MediaSessionMetadataChanged(metadata);
+
+ EXPECT_TRUE(title_label()->GetVisible());
+ EXPECT_TRUE(subtitle_label()->GetVisible());
+
+ EXPECT_EQ(metadata.title, title_label()->GetText());
+ EXPECT_EQ(metadata.source_title, subtitle_label()->GetText());
+
+ EXPECT_EQ(base::ASCIIToUTF16("title2 - artist2 - album"), accessible_name());
+
+ ExpectHistogramMetadataRecorded(
+ MediaNotificationViewModernImpl::Metadata::kTitle, 2);
+ ExpectHistogramMetadataRecorded(
+ MediaNotificationViewModernImpl::Metadata::kSource, 2);
+ ExpectHistogramMetadataRecorded(
+ MediaNotificationViewModernImpl::Metadata::kCount, 2);
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ ActionButtonsHiddenByDefault) {
+ EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kPlay));
+ EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kNextTrack));
+ EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kPreviousTrack));
+ EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kSeekForward));
+ EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kSeekBackward));
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ ActionButtonsToggleVisibility) {
+ EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kNextTrack));
+
+ EnableAction(MediaSessionAction::kNextTrack);
+
+ EXPECT_TRUE(IsActionButtonVisible(MediaSessionAction::kNextTrack));
+
+ DisableAction(MediaSessionAction::kNextTrack);
+
+ EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kNextTrack));
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, UpdateArtworkFromItem) {
+ int labels_container_width = title_label()->parent()->width();
+ gfx::Size size = view()->size();
+ EXPECT_CALL(container(), OnMediaArtworkChanged(_)).Times(2);
+ EXPECT_CALL(container(), OnColorsChanged(_, _)).Times(2);
+
+ SkBitmap image;
+ image.allocN32Pixels(10, 10);
+ image.eraseColor(SK_ColorGREEN);
+
+ EXPECT_TRUE(GetArtworkImage().isNull());
+
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, image);
+
+ ExpectHistogramArtworkRecorded(true, 1);
+
+ // The size of the labels container should not change when there is artwork.
+ EXPECT_EQ(labels_container_width, title_label()->parent()->width());
+
+ // Ensure that the labels container does not extend into the artwork bounds.
+ EXPECT_FALSE(artwork_container()->bounds().Intersects(
+ title_label()->parent()->bounds()));
+
+ // Ensure that when the image is displayed that the size of the notification
+ // was not affected.
+ EXPECT_FALSE(GetArtworkImage().isNull());
+ EXPECT_EQ(gfx::Size(10, 10), GetArtworkImage().size());
+ EXPECT_EQ(size, view()->size());
+
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, SkBitmap());
+
+ ExpectHistogramArtworkRecorded(false, 1);
+
+ // Ensure the labels container goes back to the original width now that we
+ // do not have any artwork.
+ EXPECT_EQ(labels_container_width, title_label()->parent()->width());
+
+ // Ensure that the artwork was reset and the size was still not
+ // affected.
+ EXPECT_TRUE(GetArtworkImage().isNull());
+ EXPECT_EQ(size, view()->size());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, AccessibleNodeData) {
+ ui::AXNodeData data;
+ view()->GetAccessibleNodeData(&data);
+
+ EXPECT_TRUE(
+ data.HasStringAttribute(ax::mojom::StringAttribute::kRoleDescription));
+ EXPECT_EQ(base::ASCIIToUTF16("title - artist"), accessible_name());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ Freezing_DoNotUpdateMetadata) {
+ media_session::MediaMetadata metadata;
+ metadata.title = base::ASCIIToUTF16("title2");
+ metadata.artist = base::ASCIIToUTF16("artist2");
+ metadata.album = base::ASCIIToUTF16("album");
+
+ EXPECT_CALL(container(), OnMediaSessionMetadataChanged(_)).Times(0);
+ GetItem()->Freeze(base::DoNothing());
+ GetItem()->MediaSessionMetadataChanged(metadata);
+
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, Freezing_DoNotUpdateImage) {
+ SkBitmap image;
+ image.allocN32Pixels(10, 10);
+ image.eraseColor(SK_ColorMAGENTA);
+ EXPECT_CALL(container(), OnMediaArtworkChanged(_)).Times(0);
+ EXPECT_CALL(container(), OnColorsChanged(_, _)).Times(0);
+
+ GetItem()->Freeze(base::DoNothing());
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, image);
+
+ EXPECT_TRUE(GetArtworkImage().isNull());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ Freezing_DoNotUpdatePlaybackState) {
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+
+ EXPECT_CALL(container(), OnMediaSessionInfoChanged(_)).Times(0);
+
+ GetItem()->Freeze(base::DoNothing());
+
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ GetItem()->MediaSessionInfoChanged(session_info.Clone());
+
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, Freezing_DoNotUpdateActions) {
+ EXPECT_FALSE(
+ GetButtonForAction(MediaSessionAction::kSeekForward)->GetVisible());
+
+ GetItem()->Freeze(base::DoNothing());
+ EnableAction(MediaSessionAction::kSeekForward);
+
+ EXPECT_FALSE(
+ GetButtonForAction(MediaSessionAction::kSeekForward)->GetVisible());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, Freezing_DisableInteraction) {
+ EnableAllActions();
+
+ EXPECT_EQ(0, media_controller()->next_track_count());
+
+ GetItem()->Freeze(base::DoNothing());
+
+ SimulateButtonClick(MediaSessionAction::kNextTrack);
+ GetItem()->FlushForTesting();
+
+ EXPECT_EQ(0, media_controller()->next_track_count());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, UnfreezingDoesntMissUpdates) {
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+
+ // Freeze the item and clear the metadata.
+ base::MockOnceClosure unfrozen_callback;
+ EXPECT_CALL(unfrozen_callback, Run).Times(0);
+ GetItem()->Freeze(unfrozen_callback.Get());
+ GetItem()->MediaSessionInfoChanged(nullptr);
+ GetItem()->MediaSessionMetadataChanged(base::nullopt);
+
+ // The item should be frozen and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+
+ // Bind the item to a new controller that's playing instead of paused.
+ auto new_media_controller = std::make_unique<TestMediaController>();
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ session_info->is_controllable = true;
+ GetItem()->SetController(new_media_controller->CreateMediaControllerRemote(),
+ session_info.Clone());
+
+ // The item will receive a MediaSessionInfoChanged.
+ GetItem()->MediaSessionInfoChanged(session_info.Clone());
+
+ // The item should still be frozen, and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+
+ // Update the metadata.
+ EXPECT_CALL(unfrozen_callback, Run);
+ media_session::MediaMetadata metadata;
+ metadata.title = base::ASCIIToUTF16("title2");
+ metadata.source_title = base::ASCIIToUTF16("source title 2");
+ GetItem()->MediaSessionMetadataChanged(metadata);
+
+ // The item should no longer be frozen, and we should see the updated data.
+ EXPECT_FALSE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title2"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title 2"), subtitle_label()->GetText());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ UnfreezingWaitsForArtwork_Timeout) {
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+
+ // Set an image before freezing.
+ SkBitmap initial_image;
+ initial_image.allocN32Pixels(10, 10);
+ initial_image.eraseColor(SK_ColorMAGENTA);
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, initial_image);
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Freeze the item and clear the metadata.
+ base::MockOnceClosure unfrozen_callback;
+ EXPECT_CALL(unfrozen_callback, Run).Times(0);
+ GetItem()->Freeze(unfrozen_callback.Get());
+ GetItem()->MediaSessionInfoChanged(nullptr);
+ GetItem()->MediaSessionMetadataChanged(base::nullopt);
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, SkBitmap());
+
+ // The item should be frozen and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Bind the item to a new controller that's playing instead of paused.
+ auto new_media_controller = std::make_unique<TestMediaController>();
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ session_info->is_controllable = true;
+ GetItem()->SetController(new_media_controller->CreateMediaControllerRemote(),
+ session_info.Clone());
+
+ // The item will receive a MediaSessionInfoChanged.
+ GetItem()->MediaSessionInfoChanged(session_info.Clone());
+
+ // The item should still be frozen, and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Update the metadata.
+ media_session::MediaMetadata metadata;
+ metadata.title = base::ASCIIToUTF16("title2");
+ metadata.source_title = base::ASCIIToUTF16("source title 2");
+ GetItem()->MediaSessionMetadataChanged(metadata);
+
+ // The item should still be frozen, and waiting for a new image.
+ EXPECT_TRUE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Once the freeze timer fires, the item should unfreeze even if there's no
+ // artwork.
+ EXPECT_CALL(unfrozen_callback, Run);
+ AdvanceClockMilliseconds(2600);
+
+ EXPECT_FALSE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title2"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title 2"), subtitle_label()->GetText());
+ EXPECT_TRUE(GetArtworkImage().isNull());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest, UnfreezingWaitsForActions) {
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+ EnableAction(MediaSessionAction::kNextTrack);
+ EnableAction(MediaSessionAction::kPreviousTrack);
+
+ // Freeze the item and clear the metadata and actions.
+ base::MockOnceClosure unfrozen_callback;
+ EXPECT_CALL(unfrozen_callback, Run).Times(0);
+ GetItem()->Freeze(unfrozen_callback.Get());
+ GetItem()->MediaSessionInfoChanged(nullptr);
+ GetItem()->MediaSessionMetadataChanged(base::nullopt);
+ DisableAction(MediaSessionAction::kPlay);
+ DisableAction(MediaSessionAction::kPause);
+ DisableAction(MediaSessionAction::kNextTrack);
+ DisableAction(MediaSessionAction::kPreviousTrack);
+
+ // The item should be frozen and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kNextTrack));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPreviousTrack));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+
+ // Bind the item to a new controller that's playing instead of paused.
+ auto new_media_controller = std::make_unique<TestMediaController>();
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ session_info->is_controllable = true;
+ GetItem()->SetController(new_media_controller->CreateMediaControllerRemote(),
+ session_info.Clone());
+
+ // The item will receive a MediaSessionInfoChanged.
+ GetItem()->MediaSessionInfoChanged(session_info.Clone());
+
+ // The item should still be frozen, and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kNextTrack));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPreviousTrack));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+
+ // Update the metadata.
+ media_session::MediaMetadata metadata;
+ metadata.title = base::ASCIIToUTF16("title2");
+ metadata.source_title = base::ASCIIToUTF16("source title 2");
+ GetItem()->MediaSessionMetadataChanged(metadata);
+
+ // The item should still be frozen, and waiting for new actions.
+ EXPECT_TRUE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kNextTrack));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPreviousTrack));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+
+ // Once we receive actions, the item should unfreeze.
+ EXPECT_CALL(unfrozen_callback, Run);
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+ EnableAction(MediaSessionAction::kSeekForward);
+ EnableAction(MediaSessionAction::kSeekBackward);
+
+ EXPECT_FALSE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kSeekForward));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kSeekBackward));
+ EXPECT_EQ(base::ASCIIToUTF16("title2"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title 2"), subtitle_label()->GetText());
+}
+
+TEST_F(MAYBE_MediaNotificationViewModernImplTest,
+ UnfreezingWaitsForArtwork_ReceiveArtwork) {
+ EnableAction(MediaSessionAction::kPlay);
+ EnableAction(MediaSessionAction::kPause);
+
+ // Set an image before freezing.
+ SkBitmap initial_image;
+ initial_image.allocN32Pixels(10, 10);
+ initial_image.eraseColor(SK_ColorMAGENTA);
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, initial_image);
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Freeze the item and clear the metadata.
+ base::MockOnceClosure unfrozen_callback;
+ EXPECT_CALL(unfrozen_callback, Run).Times(0);
+ GetItem()->Freeze(unfrozen_callback.Get());
+ GetItem()->MediaSessionInfoChanged(nullptr);
+ GetItem()->MediaSessionMetadataChanged(base::nullopt);
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, SkBitmap());
+
+ // The item should be frozen and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Bind the item to a new controller that's playing instead of paused.
+ auto new_media_controller = std::make_unique<TestMediaController>();
+ media_session::mojom::MediaSessionInfoPtr session_info(
+ media_session::mojom::MediaSessionInfo::New());
+ session_info->playback_state =
+ media_session::mojom::MediaPlaybackState::kPlaying;
+ session_info->is_controllable = true;
+ GetItem()->SetController(new_media_controller->CreateMediaControllerRemote(),
+ session_info.Clone());
+
+ // The item will receive a MediaSessionInfoChanged.
+ GetItem()->MediaSessionInfoChanged(session_info.Clone());
+
+ // The item should still be frozen, and the view should contain the old data.
+ EXPECT_TRUE(GetItem()->frozen());
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Update the metadata.
+ media_session::MediaMetadata metadata;
+ metadata.title = base::ASCIIToUTF16("title2");
+ metadata.source_title = base::ASCIIToUTF16("source title 2");
+ GetItem()->MediaSessionMetadataChanged(metadata);
+
+ // The item should still be frozen, and waiting for a new image.
+ EXPECT_TRUE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title"), subtitle_label()->GetText());
+ EXPECT_FALSE(GetArtworkImage().isNull());
+
+ // Once we receive artwork, the item should unfreeze.
+ EXPECT_CALL(unfrozen_callback, Run);
+ SkBitmap new_image;
+ new_image.allocN32Pixels(10, 10);
+ new_image.eraseColor(SK_ColorYELLOW);
+ GetItem()->MediaControllerImageChanged(
+ media_session::mojom::MediaSessionImageType::kArtwork, new_image);
+
+ EXPECT_FALSE(GetItem()->frozen());
+ testing::Mock::VerifyAndClearExpectations(&unfrozen_callback);
+ EXPECT_FALSE(GetButtonForAction(MediaSessionAction::kPlay));
+ EXPECT_TRUE(GetButtonForAction(MediaSessionAction::kPause));
+ EXPECT_EQ(base::ASCIIToUTF16("title2"), title_label()->GetText());
+ EXPECT_EQ(base::ASCIIToUTF16("source title 2"), subtitle_label()->GetText());
+ EXPECT_FALSE(GetArtworkImage().isNull());
+}
+
+} // namespace media_message_center
diff --git a/chromium/components/media_message_center/vector_icons/BUILD.gn b/chromium/components/media_message_center/vector_icons/BUILD.gn
index 4f197ada75d..9b6608faabb 100644
--- a/chromium/components/media_message_center/vector_icons/BUILD.gn
+++ b/chromium/components/media_message_center/vector_icons/BUILD.gn
@@ -4,18 +4,18 @@
import("//components/vector_icons/vector_icons.gni")
-aggregate_vector_icons("media_vector_icons") {
+aggregate_vector_icons2("media_vector_icons") {
icon_directory = "."
- icons = [
+ sources = [
"media_enter_pip.icon",
"media_exit_pip.icon",
"media_next_track.icon",
"media_previous_track.icon",
- "media_seek_forward.icon",
"media_seek_backward.icon",
- "play_arrow.icon",
+ "media_seek_forward.icon",
"pause.icon",
+ "play_arrow.icon",
]
}