media_string_view.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/ambient/ui/media_string_view.h"
  5. #include <memory>
  6. #include <string>
  7. #include <utility>
  8. #include "ash/ambient/ambient_constants.h"
  9. #include "ash/ambient/ui/ambient_view_ids.h"
  10. #include "ash/ambient/util/ambient_util.h"
  11. #include "ash/constants/ash_pref_names.h"
  12. #include "ash/resources/vector_icons/vector_icons.h"
  13. #include "ash/session/session_controller_impl.h"
  14. #include "ash/shell.h"
  15. #include "ash/shell_delegate.h"
  16. #include "ash/style/dark_light_mode_controller_impl.h"
  17. #include "base/bind.h"
  18. #include "base/strings/utf_string_conversions.h"
  19. #include "base/time/time.h"
  20. #include "components/prefs/pref_service.h"
  21. #include "services/media_session/public/cpp/media_session_service.h"
  22. #include "services/media_session/public/mojom/media_session.mojom.h"
  23. #include "ui/base/metadata/metadata_impl_macros.h"
  24. #include "ui/compositor/layer.h"
  25. #include "ui/compositor/paint_recorder.h"
  26. #include "ui/compositor/scoped_layer_animation_settings.h"
  27. #include "ui/gfx/font.h"
  28. #include "ui/gfx/geometry/transform.h"
  29. #include "ui/gfx/paint_vector_icon.h"
  30. #include "ui/gfx/shadow_value.h"
  31. #include "ui/gfx/skia_paint_util.h"
  32. #include "ui/gfx/text_constants.h"
  33. #include "ui/views/border.h"
  34. #include "ui/views/controls/image_view.h"
  35. #include "ui/views/controls/label.h"
  36. #include "ui/views/layout/box_layout.h"
  37. #include "ui/views/layout/flex_layout.h"
  38. #include "ui/views/layout/flex_layout_types.h"
  39. namespace ash {
  40. namespace {
  41. // A layer delegate used for mask layer, with left and right gradient fading out
  42. // zones.
  43. class FadeoutLayerDelegate : public ui::LayerDelegate {
  44. public:
  45. FadeoutLayerDelegate() : layer_(ui::LAYER_TEXTURED) {
  46. layer_.set_delegate(this);
  47. layer_.SetFillsBoundsOpaquely(false);
  48. }
  49. ~FadeoutLayerDelegate() override { layer_.set_delegate(nullptr); }
  50. ui::Layer* layer() { return &layer_; }
  51. private:
  52. // ui::LayerDelegate:
  53. void OnPaintLayer(const ui::PaintContext& context) override {
  54. const gfx::Size& size = layer()->size();
  55. gfx::Rect left_rect(0, 0, kMediaStringGradientWidthDip, size.height());
  56. gfx::Rect right_rect(size.width() - kMediaStringGradientWidthDip, 0,
  57. kMediaStringGradientWidthDip, size.height());
  58. views::PaintInfo paint_info =
  59. views::PaintInfo::CreateRootPaintInfo(context, size);
  60. const auto& prs = paint_info.paint_recording_size();
  61. // Pass the scale factor when constructing PaintRecorder so the MaskLayer
  62. // size is not incorrectly rounded (see https://crbug.com/921274).
  63. ui::PaintRecorder recorder(context, paint_info.paint_recording_size(),
  64. static_cast<float>(prs.width()) / size.width(),
  65. static_cast<float>(prs.height()) / size.height(),
  66. nullptr);
  67. gfx::Canvas* canvas = recorder.canvas();
  68. // Clear the canvas.
  69. canvas->DrawColor(SK_ColorBLACK, SkBlendMode::kSrc);
  70. // Draw left gradient zone.
  71. cc::PaintFlags flags;
  72. flags.setBlendMode(SkBlendMode::kSrc);
  73. flags.setAntiAlias(false);
  74. flags.setShader(gfx::CreateGradientShader(
  75. gfx::Point(), gfx::Point(kMediaStringGradientWidthDip, 0),
  76. SK_ColorTRANSPARENT, SK_ColorBLACK));
  77. canvas->DrawRect(left_rect, flags);
  78. // Draw right gradient zone.
  79. flags.setShader(gfx::CreateGradientShader(
  80. gfx::Point(size.width() - kMediaStringGradientWidthDip, 0),
  81. gfx::Point(size.width(), 0), SK_ColorBLACK, SK_ColorTRANSPARENT));
  82. canvas->DrawRect(right_rect, flags);
  83. }
  84. void OnDeviceScaleFactorChanged(float old_device_scale_factor,
  85. float new_device_scale_factor) override {}
  86. ui::Layer layer_;
  87. };
  88. // Typography.
  89. constexpr char16_t kMiddleDotSeparator[] = u" • ";
  90. constexpr int kMusicNoteIconSizeDip = 20;
  91. // Returns true if we should show media string for ambient mode on lock-screen
  92. // based on user pref. We should keep the same user policy here as the
  93. // lock-screen media controls to avoid exposing user data on lock-screen without
  94. // consent.
  95. bool ShouldShowOnLockScreen() {
  96. PrefService* pref =
  97. Shell::Get()->session_controller()->GetPrimaryUserPrefService();
  98. DCHECK(pref);
  99. return pref->GetBoolean(prefs::kLockScreenMediaControlsEnabled);
  100. }
  101. } // namespace
  102. MediaStringView::MediaStringView(Settings settings)
  103. : settings_(std::move(settings)) {
  104. SetID(AmbientViewID::kAmbientMediaStringView);
  105. InitLayout();
  106. }
  107. MediaStringView::~MediaStringView() = default;
  108. void MediaStringView::OnThemeChanged() {
  109. views::View::OnThemeChanged();
  110. media_text_->SetShadows(ambient::util::GetTextShadowValues(
  111. GetColorProvider(), settings_.text_shadow_elevation));
  112. const bool dark_mode_enabled =
  113. DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
  114. DCHECK(icon_);
  115. icon_->SetImage(gfx::CreateVectorIcon(kMusicNoteIcon, kMusicNoteIconSizeDip,
  116. dark_mode_enabled
  117. ? settings_.icon_dark_mode_color
  118. : settings_.icon_light_mode_color));
  119. DCHECK(media_text_);
  120. media_text_->SetEnabledColor(dark_mode_enabled
  121. ? settings_.text_dark_mode_color
  122. : settings_.text_light_mode_color);
  123. }
  124. void MediaStringView::OnViewBoundsChanged(views::View* observed_view) {
  125. UpdateMaskLayer();
  126. }
  127. void MediaStringView::MediaSessionInfoChanged(
  128. media_session::mojom::MediaSessionInfoPtr session_info) {
  129. if (ambient::util::IsShowing(LockScreen::ScreenType::kLock) &&
  130. !ShouldShowOnLockScreen()) {
  131. return;
  132. }
  133. // Don't show the media string if session info is unavailable, or the active
  134. // session is marked as sensitive.
  135. if (!session_info || session_info->is_sensitive) {
  136. SetVisible(false);
  137. return;
  138. }
  139. bool is_paused = session_info->playback_state ==
  140. media_session::mojom::MediaPlaybackState::kPaused;
  141. // Don't show the media string if paused.
  142. SetVisible(!is_paused);
  143. }
  144. void MediaStringView::MediaSessionMetadataChanged(
  145. const absl::optional<media_session::MediaMetadata>& metadata) {
  146. media_session::MediaMetadata session_metadata =
  147. metadata.value_or(media_session::MediaMetadata());
  148. std::u16string media_string;
  149. std::u16string middle_dot = kMiddleDotSeparator;
  150. if (!session_metadata.title.empty() && !session_metadata.artist.empty()) {
  151. media_string =
  152. session_metadata.title + middle_dot + session_metadata.artist;
  153. } else if (!session_metadata.title.empty()) {
  154. media_string = session_metadata.title;
  155. } else {
  156. media_string = session_metadata.artist;
  157. }
  158. // Reset text and stop any ongoing animation.
  159. media_text_->SetText(std::u16string());
  160. media_text_->layer()->GetAnimator()->StopAnimating();
  161. media_text_->SetText(media_string);
  162. media_text_->layer()->SetTransform(gfx::Transform());
  163. const auto& text_size = media_text_->GetPreferredSize();
  164. const int text_width = text_size.width();
  165. media_text_container_->SetPreferredSize(gfx::Size(
  166. std::min(kMediaStringMaxWidthDip, text_width), text_size.height()));
  167. if (NeedToAnimate()) {
  168. media_text_->SetText(media_string + middle_dot + media_string + middle_dot);
  169. ScheduleScrolling(/*is_initial=*/true);
  170. }
  171. }
  172. void MediaStringView::OnImplicitAnimationsCompleted() {
  173. if (!NeedToAnimate())
  174. return;
  175. ScheduleScrolling(/*is_initial=*/false);
  176. }
  177. void MediaStringView::InitLayout() {
  178. // This view will be drawn on its own layer instead of the layer of
  179. // |PhotoView| which has a solid black background.
  180. SetPaintToLayer();
  181. layer()->SetFillsBoundsOpaquely(false);
  182. constexpr int kChildSpacingDip = 8;
  183. auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
  184. views::BoxLayout::Orientation::kHorizontal));
  185. layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
  186. layout->set_cross_axis_alignment(
  187. views::BoxLayout::CrossAxisAlignment::kCenter);
  188. layout->set_between_child_spacing(kChildSpacingDip);
  189. icon_ = AddChildView(std::make_unique<views::ImageView>());
  190. icon_->SetPreferredSize(
  191. gfx::Size(kMusicNoteIconSizeDip, kMusicNoteIconSizeDip));
  192. media_text_container_ = AddChildView(std::make_unique<views::View>());
  193. media_text_container_->SetPaintToLayer();
  194. media_text_container_->layer()->SetFillsBoundsOpaquely(false);
  195. media_text_container_->layer()->SetMasksToBounds(true);
  196. auto* text_layout = media_text_container_->SetLayoutManager(
  197. std::make_unique<views::FlexLayout>());
  198. text_layout->SetOrientation(views::LayoutOrientation::kHorizontal);
  199. text_layout->SetMainAxisAlignment(views::LayoutAlignment::kStart);
  200. text_layout->SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
  201. observed_view_.Observe(media_text_container_);
  202. media_text_ =
  203. media_text_container_->AddChildView(std::make_unique<views::Label>());
  204. media_text_->SetPaintToLayer();
  205. media_text_->layer()->SetFillsBoundsOpaquely(false);
  206. // Defines the appearance.
  207. constexpr int kDefaultFontSizeDip = 64;
  208. constexpr int kMediaStringFontSizeDip = 18;
  209. media_text_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_TO_HEAD);
  210. media_text_->SetVerticalAlignment(gfx::VerticalAlignment::ALIGN_MIDDLE);
  211. media_text_->SetAutoColorReadabilityEnabled(false);
  212. media_text_->SetFontList(
  213. ambient::util::GetDefaultFontlist()
  214. .DeriveWithSizeDelta(kMediaStringFontSizeDip - kDefaultFontSizeDip)
  215. .DeriveWithWeight(gfx::Font::Weight::MEDIUM));
  216. media_text_->SetElideBehavior(gfx::ElideBehavior::NO_ELIDE);
  217. gfx::Insets shadow_insets =
  218. gfx::ShadowValue::GetMargin(ambient::util::GetTextShadowValues(
  219. nullptr, settings_.text_shadow_elevation));
  220. // Compensate the shadow insets to put the text middle align with the icon.
  221. media_text_->SetBorder(views::CreateEmptyBorder(
  222. gfx::Insets::TLBR(-shadow_insets.bottom(), 0, -shadow_insets.top(), 0)));
  223. BindMediaControllerObserver();
  224. }
  225. void MediaStringView::BindMediaControllerObserver() {
  226. media_session::MediaSessionService* service =
  227. Shell::Get()->shell_delegate()->GetMediaSessionService();
  228. // Service might be unavailable under some test environments.
  229. if (!service)
  230. return;
  231. // Binds to the MediaControllerManager and create a MediaController for the
  232. // current active media session so that we can observe it.
  233. mojo::Remote<media_session::mojom::MediaControllerManager>
  234. controller_manager_remote;
  235. service->BindMediaControllerManager(
  236. controller_manager_remote.BindNewPipeAndPassReceiver());
  237. controller_manager_remote->CreateActiveMediaController(
  238. media_controller_remote_.BindNewPipeAndPassReceiver());
  239. // Observe the active media controller for changes.
  240. media_controller_remote_->AddObserver(
  241. observer_receiver_.BindNewPipeAndPassRemote());
  242. }
  243. void MediaStringView::UpdateMaskLayer() {
  244. if (!NeedToAnimate()) {
  245. media_text_container_->layer()->SetMaskLayer(nullptr);
  246. return;
  247. }
  248. if (!fadeout_layer_delegate_) {
  249. fadeout_layer_delegate_ = std::make_unique<FadeoutLayerDelegate>();
  250. fadeout_layer_delegate_->layer()->SetBounds(
  251. media_text_container_->layer()->bounds());
  252. }
  253. media_text_container_->layer()->SetMaskLayer(
  254. fadeout_layer_delegate_->layer());
  255. }
  256. bool MediaStringView::NeedToAnimate() const {
  257. return media_text_->GetPreferredSize().width() >
  258. media_text_container_->GetPreferredSize().width();
  259. }
  260. gfx::Transform MediaStringView::GetMediaTextTransform(bool is_initial) {
  261. gfx::Transform transform;
  262. if (is_initial) {
  263. // Start animation half way of |media_text_container_|.
  264. transform.Translate(kMediaStringMaxWidthDip / 2, 0);
  265. }
  266. return transform;
  267. }
  268. void MediaStringView::ScheduleScrolling(bool is_initial) {
  269. if (!GetVisible())
  270. return;
  271. base::SequencedTaskRunnerHandle::Get()->PostTask(
  272. FROM_HERE, base::BindOnce(&MediaStringView::StartScrolling,
  273. weak_factory_.GetWeakPtr(), is_initial));
  274. }
  275. void MediaStringView::StartScrolling(bool is_initial) {
  276. ui::Layer* text_layer = media_text_->layer();
  277. text_layer->SetTransform(GetMediaTextTransform(is_initial));
  278. {
  279. // Desired speed is 10 seconds for kMediaStringMaxWidthDip.
  280. const int text_width = media_text_->GetPreferredSize().width();
  281. const int shadow_width = gfx::ShadowValue::GetMargin(
  282. ambient::util::GetTextShadowValues(
  283. nullptr, settings_.text_shadow_elevation))
  284. .width();
  285. const int start_x = text_layer->GetTargetTransform().To2dTranslation().x();
  286. const int end_x = -(text_width + shadow_width) / 2;
  287. const int transform_distance = start_x - end_x;
  288. const base::TimeDelta kScrollingDuration =
  289. base::Seconds(10) * transform_distance / kMediaStringMaxWidthDip;
  290. ui::ScopedLayerAnimationSettings animation(text_layer->GetAnimator());
  291. animation.SetTransitionDuration(kScrollingDuration);
  292. animation.SetTweenType(gfx::Tween::LINEAR);
  293. animation.SetPreemptionStrategy(
  294. ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
  295. animation.AddObserver(this);
  296. gfx::Transform transform;
  297. transform.Translate(end_x, 0);
  298. text_layer->SetTransform(transform);
  299. }
  300. }
  301. BEGIN_METADATA(MediaStringView, views::View)
  302. END_METADATA
  303. } // namespace ash