1 // Copyright 2019 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 
5 #include "ash/login/ui/lock_screen_media_controls_view.h"
6 
7 #include "ash/login/ui/lock_contents_view.h"
8 #include "ash/login/ui/media_controls_header_view.h"
9 #include "ash/login/ui/views_utils.h"
10 #include "ash/media/media_controller_impl.h"
11 #include "ash/resources/vector_icons/vector_icons.h"
12 #include "ash/session/session_controller_impl.h"
13 #include "ash/shell.h"
14 #include "ash/shell_delegate.h"
15 #include "ash/strings/grit/ash_strings.h"
16 #include "ash/style/ash_color_provider.h"
17 #include "base/metrics/histogram_functions.h"
18 #include "base/power_monitor/power_monitor.h"
19 #include "base/threading/thread_task_runner_handle.h"
20 #include "components/media_message_center/media_controls_progress_view.h"
21 #include "components/media_message_center/media_notification_util.h"
22 #include "components/vector_icons/vector_icons.h"
23 #include "services/media_session/public/cpp/util.h"
24 #include "services/media_session/public/mojom/media_session.mojom.h"
25 #include "ui/accessibility/ax_enums.mojom.h"
26 #include "ui/accessibility/ax_node_data.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/compositor/scoped_layer_animation_settings.h"
29 #include "ui/gfx/font.h"
30 #include "ui/gfx/font_list.h"
31 #include "ui/gfx/paint_vector_icon.h"
32 #include "ui/message_center/message_center.h"
33 #include "ui/message_center/vector_icons.h"
34 #include "ui/views/animation/ink_drop_highlight.h"
35 #include "ui/views/background.h"
36 #include "ui/views/controls/button/image_button_factory.h"
37 #include "ui/views/controls/highlight_path_generator.h"
38 #include "ui/views/controls/image_view.h"
39 #include "ui/views/controls/label.h"
40 #include "ui/views/layout/box_layout.h"
41 #include "ui/views/layout/layout_provider.h"
42 #include "ui/views/metadata/metadata_impl_macros.h"
43 
44 namespace ash {
45 
46 using media_session::mojom::MediaSessionAction;
47 
48 namespace {
49 
50 constexpr SkColor kProgressBarForeground = gfx::kGoogleBlue300;
51 constexpr SkColor kProgressBarBackground =
52     SkColorSetA(gfx::kGoogleBlue300, 0x4C);  // 30%
53 
54 // Maximum number of actions that should be displayed on |button_row_|.
55 constexpr size_t kMaxActions = 5;
56 
57 // Dimensions.
58 constexpr gfx::Insets kMediaControlsInsets = gfx::Insets(16, 16, 16, 16);
59 constexpr int kMediaControlsCornerRadius = 16;
60 constexpr int kMinimumSourceIconSize = 16;
61 constexpr int kDesiredSourceIconSize = 20;
62 constexpr int kMinimumArtworkSize = 30;
63 constexpr int kDesiredArtworkSize = 48;
64 constexpr int kArtworkRowPadding = 16;
65 constexpr gfx::Insets kArtworkRowInsets = gfx::Insets(24, 0, 9, 0);
66 constexpr gfx::Size kArtworkRowPreferredSize =
67     gfx::Size(328, kDesiredArtworkSize);
68 constexpr int kMediaButtonRowPadding = 16;
69 constexpr gfx::Insets kButtonRowInsets = gfx::Insets(4, 0, 0, 0);
70 constexpr int kPlayPauseIconSize = 40;
71 constexpr int kMediaControlsIconSize = 24;
72 constexpr gfx::Size kPlayPauseButtonSize = gfx::Size(72, 72);
73 constexpr gfx::Size kMediaControlsButtonSize = gfx::Size(48, 48);
74 constexpr gfx::Size kMediaControlsButtonRowSize =
75     gfx::Size(328, kPlayPauseButtonSize.height());
76 constexpr gfx::Size kMediaButtonGroupSize =
77     gfx::Size(2 * kMediaControlsButtonSize.width() + kMediaButtonRowPadding,
78               kPlayPauseButtonSize.height());
79 constexpr int kArtworkCornerRadius = 4;
80 
81 constexpr int kDragVelocityThreshold = 6;
82 constexpr int kDistanceDismissalThreshold = 20;
83 constexpr base::TimeDelta kAnimationDuration =
84     base::TimeDelta::FromMilliseconds(200);
85 
86 // How long to wait (in milliseconds) for a new media session to begin.
87 constexpr base::TimeDelta kNextMediaDelay =
88     base::TimeDelta::FromMilliseconds(2500);
89 
90 // Scales |size| to fit |view_size| while preserving proportions.
ScaleSizeToFitView(const gfx::Size & size,const gfx::Size & view_size)91 gfx::Size ScaleSizeToFitView(const gfx::Size& size,
92                              const gfx::Size& view_size) {
93   // If |size| is too big in either dimension or two small in both
94   // dimensions, scale it appropriately.
95   if ((size.width() > view_size.width() ||
96        size.height() > view_size.height()) ||
97       (size.width() < view_size.width() &&
98        size.height() < view_size.height())) {
99     const float scale =
100         std::min(view_size.width() / static_cast<float>(size.width()),
101                  view_size.height() / static_cast<float>(size.height()));
102     return gfx::ScaleToFlooredSize(size, scale);
103   }
104 
105   return size;
106 }
107 
GetVectorIconForMediaAction(MediaSessionAction action)108 const gfx::VectorIcon& GetVectorIconForMediaAction(MediaSessionAction action) {
109   switch (action) {
110     case MediaSessionAction::kPreviousTrack:
111       return vector_icons::kMediaPreviousTrackIcon;
112     case MediaSessionAction::kPause:
113       return vector_icons::kPauseIcon;
114     case MediaSessionAction::kNextTrack:
115       return vector_icons::kMediaNextTrackIcon;
116     case MediaSessionAction::kPlay:
117       return vector_icons::kPlayArrowIcon;
118     case MediaSessionAction::kSeekBackward:
119       return vector_icons::kMediaSeekBackwardIcon;
120     case MediaSessionAction::kSeekForward:
121       return vector_icons::kMediaSeekForwardIcon;
122 
123     // The following actions are not yet supported on the controls.
124     case MediaSessionAction::kStop:
125     case MediaSessionAction::kSkipAd:
126     case MediaSessionAction::kSeekTo:
127     case MediaSessionAction::kScrubTo:
128     case MediaSessionAction::kEnterPictureInPicture:
129     case MediaSessionAction::kExitPictureInPicture:
130     case MediaSessionAction::kSwitchAudioDevice:
131       NOTREACHED();
132       break;
133   }
134 
135   NOTREACHED();
136   return gfx::kNoneIcon;
137 }
138 
139 // MediaActionButton is an image button with a custom ink drop mask.
140 class MediaActionButton : public views::ImageButton {
141  public:
MediaActionButton(LockScreenMediaControlsView * view,int icon_size,MediaSessionAction action,const base::string16 & accessible_name)142   MediaActionButton(LockScreenMediaControlsView* view,
143                     int icon_size,
144                     MediaSessionAction action,
145                     const base::string16& accessible_name)
146       : views::ImageButton(base::BindRepeating(
147             // Handle dynamically-updated button tags without rebinding.
148             [](LockScreenMediaControlsView* controls,
149                MediaActionButton* button) {
150               controls->ButtonPressed(
151                   media_message_center::GetActionFromButtonTag(*button));
152             },
153             view,
154             this)),
155         icon_size_(icon_size) {
156     SetInkDropMode(views::Button::InkDropMode::ON);
157     SetHasInkDropActionOnClick(true);
158     SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
159     SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
160     SetBorder(
161         views::CreateEmptyBorder(views::LayoutProvider::Get()->GetInsetsMetric(
162             views::INSETS_VECTOR_IMAGE_BUTTON)));
163 
164     const bool is_play_pause = action == MediaSessionAction::kPause ||
165                                action == MediaSessionAction::kPlay;
166     SetPreferredSize(is_play_pause ? kPlayPauseButtonSize
167                                    : kMediaControlsButtonSize);
168     SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
169     SetAction(action, accessible_name);
170 
171     SetInstallFocusRingOnFocus(true);
172     login_views_utils::ConfigureRectFocusRingCircleInkDrop(this, focus_ring(),
173                                                            base::nullopt);
174   }
175 
176   ~MediaActionButton() override = default;
177 
SetAction(MediaSessionAction action,const base::string16 & accessible_name)178   void SetAction(MediaSessionAction action,
179                  const base::string16& accessible_name) {
180     set_tag(static_cast<int>(action));
181     SetTooltipText(accessible_name);
182     views::SetImageFromVectorIcon(
183         this, GetVectorIconForMediaAction(action), icon_size_,
184         AshColorProvider::Get()->GetContentLayerColor(
185             AshColorProvider::ContentLayerType::kIconColorPrimary));
186   }
187 
CreateInkDropHighlight() const188   std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
189       const override {
190     return std::make_unique<views::InkDropHighlight>(gfx::SizeF(size()),
191                                                      GetInkDropBaseColor());
192   }
193 
194  private:
195   int const icon_size_;
196 
197   DISALLOW_COPY_AND_ASSIGN(MediaActionButton);
198 };
199 
200 }  // namespace
201 
202 const char LockScreenMediaControlsView::kMediaControlsHideHistogramName[] =
203     "Media.LockScreenControls.Hide";
204 
205 const char LockScreenMediaControlsView::kMediaControlsShownHistogramName[] =
206     "Media.LockScreenControls.Shown";
207 
208 const char
209     LockScreenMediaControlsView::kMediaControlsUserActionHistogramName[] =
210         "Media.LockScreenControls.UserAction";
211 
212 LockScreenMediaControlsView::Callbacks::Callbacks() = default;
213 
214 LockScreenMediaControlsView::Callbacks::~Callbacks() = default;
215 
LockScreenMediaControlsView(const Callbacks & callbacks)216 LockScreenMediaControlsView::LockScreenMediaControlsView(
217     const Callbacks& callbacks)
218     : media_controls_enabled_(callbacks.media_controls_enabled),
219       hide_media_controls_(callbacks.hide_media_controls),
220       show_media_controls_(callbacks.show_media_controls) {
221   DCHECK(callbacks.media_controls_enabled);
222   DCHECK(callbacks.hide_media_controls);
223   DCHECK(callbacks.show_media_controls);
224 
225   // Media controls should observe power events and handle the case of being
226   // created in suspended state.
227   base::PowerMonitor::AddObserver(this);
228   if (base::PowerMonitor::IsProcessSuspended()) {
229     // Post OnSuspend call to run after LockContentsView is initialized.
230     base::ThreadTaskRunnerHandle::Get()->PostTask(
231         FROM_HERE, base::BindOnce(&LockScreenMediaControlsView::OnSuspend,
232                                   weak_ptr_factory_.GetWeakPtr()));
233   }
234 
235   // Media controls have not been dismissed initially.
236   Shell::Get()->media_controller()->SetMediaControlsDismissed(false);
237 
238   SetNotifyEnterExitOnChild(true);
239 
240   contents_view_ = AddChildView(std::make_unique<views::View>());
241   contents_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
242       views::BoxLayout::Orientation::kVertical, kMediaControlsInsets));
243   contents_view_->SetBackground(views::CreateRoundedRectBackground(
244       AshColorProvider::Get()->GetBaseLayerColor(
245           AshColorProvider::BaseLayerType::kTransparent80),
246       kMediaControlsCornerRadius));
247 
248   contents_view_->SetPaintToLayer();  // Needed for opacity animation.
249   contents_view_->layer()->SetFillsBoundsOpaquely(false);
250 
251   // |header_row_| contains the app icon and source title of the current media
252   // session. It also contains the close button.
253   header_row_ = contents_view_->AddChildView(
254       std::make_unique<MediaControlsHeaderView>(base::BindRepeating(
255           &LockScreenMediaControlsView::Dismiss, base::Unretained(this))));
256 
257   // |artwork_row| contains the session artwork, artist and track info.
258   auto artwork_row = std::make_unique<NonAccessibleView>();
259   artwork_row->SetPreferredSize(kArtworkRowPreferredSize);
260   auto* artwork_row_layout =
261       artwork_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
262           views::BoxLayout::Orientation::kHorizontal, kArtworkRowInsets,
263           kArtworkRowPadding));
264   artwork_row_layout->set_cross_axis_alignment(
265       views::BoxLayout::CrossAxisAlignment::kCenter);
266   artwork_row_layout->set_main_axis_alignment(
267       views::BoxLayout::MainAxisAlignment::kStart);
268 
269   auto session_artwork = std::make_unique<views::ImageView>();
270   session_artwork->SetPreferredSize(
271       gfx::Size(kDesiredArtworkSize, kDesiredArtworkSize));
272   session_artwork_ = artwork_row->AddChildView(std::move(session_artwork));
273   session_artwork_->SetVisible(false);
274 
275   // |track_column| contains the title and artist labels of the current media
276   // session.
277   auto track_column = std::make_unique<NonAccessibleView>();
278   auto* track_column_layout =
279       track_column->SetLayoutManager(std::make_unique<views::BoxLayout>(
280           views::BoxLayout::Orientation::kVertical));
281   track_column_layout->set_main_axis_alignment(
282       views::BoxLayout::MainAxisAlignment::kCenter);
283 
284   const gfx::FontList& base_font_list = views::Label::GetDefaultFontList();
285 
286   auto title_label = std::make_unique<views::Label>();
287   title_label->SetFontList(base_font_list.Derive(
288       2, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::BOLD));
289   title_label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
290       AshColorProvider::ContentLayerType::kTextColorPrimary));
291   title_label->SetAutoColorReadabilityEnabled(false);
292   title_label->SetElideBehavior(gfx::ELIDE_TAIL);
293   title_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
294   title_label_ = track_column->AddChildView(std::move(title_label));
295 
296   auto artist_label = std::make_unique<views::Label>();
297   artist_label->SetFontList(base_font_list.Derive(
298       0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::LIGHT));
299   artist_label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
300       AshColorProvider::ContentLayerType::kTextColorSecondary));
301   artist_label->SetAutoColorReadabilityEnabled(false);
302   artist_label->SetElideBehavior(gfx::ELIDE_TAIL);
303   artist_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
304   artist_label_ = track_column->AddChildView(std::move(artist_label));
305 
306   artwork_row->AddChildView(std::move(track_column));
307 
308   contents_view_->AddChildView(std::move(artwork_row));
309 
310   auto progress_view =
311       std::make_unique<media_message_center::MediaControlsProgressView>(
312           base::BindRepeating(&LockScreenMediaControlsView::SeekTo,
313                               base::Unretained(this)));
314   progress_view->SetForegroundColor(kProgressBarForeground);
315   progress_view->SetBackgroundColor(kProgressBarBackground);
316   progress_ = contents_view_->AddChildView(std::move(progress_view));
317 
318   // |button_row_| contains the buttons for controlling playback.
319   auto button_row = std::make_unique<NonAccessibleView>();
320 
321   // left_control_group contains previous_track_button and seek_backward_button.
322   auto left_control_group = std::make_unique<NonAccessibleView>();
323 
324   // right_control_group contains next_track_button and seek_forward_button.
325   auto right_control_group = std::make_unique<NonAccessibleView>();
326 
327   auto* button_row_layout =
328       button_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
329           views::BoxLayout::Orientation::kHorizontal, kButtonRowInsets,
330           kMediaButtonRowPadding));
331   button_row_layout->set_cross_axis_alignment(
332       views::BoxLayout::CrossAxisAlignment::kCenter);
333   button_row_layout->set_main_axis_alignment(
334       views::BoxLayout::MainAxisAlignment::kCenter);
335 
336   auto* left_control_group_layout =
337       left_control_group->SetLayoutManager(std::make_unique<views::BoxLayout>(
338           views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
339           kMediaButtonRowPadding));
340   left_control_group_layout->set_cross_axis_alignment(
341       views::BoxLayout::CrossAxisAlignment::kCenter);
342   left_control_group_layout->set_main_axis_alignment(
343       views::BoxLayout::MainAxisAlignment::kEnd);
344 
345   auto* right_control_group_layout =
346       right_control_group->SetLayoutManager(std::make_unique<views::BoxLayout>(
347           views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
348           kMediaButtonRowPadding));
349   right_control_group_layout->set_cross_axis_alignment(
350       views::BoxLayout::CrossAxisAlignment::kCenter);
351   right_control_group_layout->set_main_axis_alignment(
352       views::BoxLayout::MainAxisAlignment::kStart);
353 
354   button_row->SetPreferredSize(kMediaControlsButtonRowSize);
355   button_row_ = contents_view_->AddChildView(std::move(button_row));
356 
357   left_control_group->SetPreferredSize(kMediaButtonGroupSize);
358   right_control_group->SetPreferredSize(kMediaButtonGroupSize);
359 
360   media_action_buttons_.push_back(
361       left_control_group->AddChildView(std::make_unique<MediaActionButton>(
362           this, kMediaControlsIconSize, MediaSessionAction::kPreviousTrack,
363           l10n_util::GetStringUTF16(
364               IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACTION_PREVIOUS_TRACK))));
365 
366   media_action_buttons_.push_back(
367       left_control_group->AddChildView(std::make_unique<MediaActionButton>(
368           this, kMediaControlsIconSize, MediaSessionAction::kSeekBackward,
369           l10n_util::GetStringUTF16(
370               IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACTION_SEEK_BACKWARD))));
371 
372   button_row_->AddChildView(std::move(left_control_group));
373 
374   // |play_pause_button_| toggles playback. If the current media is playing, the
375   // tag will be |MediaSessionAction::kPause|. If the current media is paused,
376   // the tag will be |MediaSessionAction::kPlay|.
377   play_pause_button_ =
378       button_row_->AddChildView(std::make_unique<MediaActionButton>(
379           this, kPlayPauseIconSize, MediaSessionAction::kPause,
380           l10n_util::GetStringUTF16(
381               IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACTION_PAUSE)));
382   media_action_buttons_.push_back(play_pause_button_);
383 
384   media_action_buttons_.push_back(
385       right_control_group->AddChildView(std::make_unique<MediaActionButton>(
386           this, kMediaControlsIconSize, MediaSessionAction::kSeekForward,
387           l10n_util::GetStringUTF16(
388               IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACTION_SEEK_FORWARD))));
389 
390   media_action_buttons_.push_back(
391       right_control_group->AddChildView(std::make_unique<MediaActionButton>(
392           this, kMediaControlsIconSize, MediaSessionAction::kNextTrack,
393           l10n_util::GetStringUTF16(
394               IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACTION_NEXT_TRACK))));
395 
396   button_row_->AddChildView(std::move(right_control_group));
397 
398   // Set child view data to default values initially, until the media controller
399   // observers are triggered by a change in media session state.
400   MediaSessionMetadataChanged(base::nullopt);
401   MediaSessionPositionChanged(base::nullopt);
402   MediaControllerImageChanged(
403       media_session::mojom::MediaSessionImageType::kSourceIcon, SkBitmap());
404   SetArtwork(base::nullopt);
405 
406   // |service| can be null in tests.
407   media_session::MediaSessionService* service =
408       Shell::Get()->shell_delegate()->GetMediaSessionService();
409   if (!service)
410     return;
411 
412   // Connect to the MediaControllerManager and create a MediaController that
413   // controls the active session so we can observe it.
414   mojo::Remote<media_session::mojom::MediaControllerManager>
415       controller_manager_remote;
416   service->BindMediaControllerManager(
417       controller_manager_remote.BindNewPipeAndPassReceiver());
418   controller_manager_remote->CreateActiveMediaController(
419       media_controller_remote_.BindNewPipeAndPassReceiver());
420 
421   // Observe the active media controller for changes.
422   media_controller_remote_->AddObserver(
423       observer_receiver_.BindNewPipeAndPassRemote());
424 
425   media_controller_remote_->ObserveImages(
426       media_session::mojom::MediaSessionImageType::kArtwork,
427       kMinimumArtworkSize, kDesiredArtworkSize,
428       artwork_observer_receiver_.BindNewPipeAndPassRemote());
429 
430   media_controller_remote_->ObserveImages(
431       media_session::mojom::MediaSessionImageType::kSourceIcon,
432       kMinimumSourceIconSize, kDesiredSourceIconSize,
433       icon_observer_receiver_.BindNewPipeAndPassRemote());
434 }
435 
~LockScreenMediaControlsView()436 LockScreenMediaControlsView::~LockScreenMediaControlsView() {
437   // If the screen is now unlocked and we were not hidden for another reason
438   // then we are being hidden because the device is now unlocked.
439   if (shown_ == Shown::kShown) {
440     if (!hide_reason_ && !Shell::Get()->session_controller()->IsScreenLocked())
441       hide_reason_ = HideReason::kUnlocked;
442 
443     // Only record hide reason if there is one. The value could be missing
444     // when ash shuts down with the media controls.
445     if (hide_reason_) {
446       base::UmaHistogramEnumeration(kMediaControlsHideHistogramName,
447                                     *hide_reason_);
448     }
449   }
450 
451   base::PowerMonitor::RemoveObserver(this);
452 }
453 
CalculatePreferredSize() const454 gfx::Size LockScreenMediaControlsView::CalculatePreferredSize() const {
455   return contents_view_->GetPreferredSize();
456 }
457 
Layout()458 void LockScreenMediaControlsView::Layout() {
459   contents_view_->SetBoundsRect(GetContentsBounds());
460 }
461 
GetAccessibleNodeData(ui::AXNodeData * node_data)462 void LockScreenMediaControlsView::GetAccessibleNodeData(
463     ui::AXNodeData* node_data) {
464   node_data->role = ax::mojom::Role::kListItem;
465   node_data->AddStringAttribute(
466       ax::mojom::StringAttribute::kRoleDescription,
467       l10n_util::GetStringUTF8(
468           IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACCESSIBLE_NAME));
469 
470   if (!accessible_name_.empty())
471     node_data->SetName(accessible_name_);
472 }
473 
OnMouseEntered(const ui::MouseEvent & event)474 void LockScreenMediaControlsView::OnMouseEntered(const ui::MouseEvent& event) {
475   if (is_in_drag_ || contents_view_->layer()->GetAnimator()->is_animating())
476     return;
477 
478   header_row_->SetCloseButtonVisibility(true);
479 }
480 
OnMouseExited(const ui::MouseEvent & event)481 void LockScreenMediaControlsView::OnMouseExited(const ui::MouseEvent& event) {
482   if (is_in_drag_ || contents_view_->layer()->GetAnimator()->is_animating())
483     return;
484 
485   header_row_->SetCloseButtonVisibility(false);
486 }
487 
MediaSessionInfoChanged(media_session::mojom::MediaSessionInfoPtr session_info)488 void LockScreenMediaControlsView::MediaSessionInfoChanged(
489     media_session::mojom::MediaSessionInfoPtr session_info) {
490   if (hide_controls_timer_->IsRunning())
491     return;
492 
493   // If the controls are disabled then don't show the controls.
494   if (!media_controls_enabled_.Run()) {
495     SetShown(Shown::kNotShownControlsDisabled);
496     return;
497   }
498 
499   // If there is no session to show then don't show the controls.
500   if (!session_info) {
501     SetShown(Shown::kNotShownNoSession);
502     return;
503   }
504 
505   // If the session is marked as sensitive then don't show the controls.
506   if (session_info->is_sensitive && !IsDrawn()) {
507     SetShown(Shown::kNotShownSessionSensitive);
508     return;
509   }
510 
511   bool is_paused = session_info->playback_state ==
512                    media_session::mojom::MediaPlaybackState::kPaused;
513 
514   // If the screen is locked while media is paused then don't show the controls.
515   if (is_paused && !IsDrawn()) {
516     SetShown(Shown::kNotShownSessionPaused);
517     return;
518   }
519 
520   if (!IsDrawn())
521     SetShown(Shown::kShown);
522 
523   SetIsPlaying(!is_paused);
524 }
525 
MediaSessionMetadataChanged(const base::Optional<media_session::MediaMetadata> & metadata)526 void LockScreenMediaControlsView::MediaSessionMetadataChanged(
527     const base::Optional<media_session::MediaMetadata>& metadata) {
528   if (hide_controls_timer_->IsRunning())
529     return;
530 
531   media_session::MediaMetadata session_metadata =
532       metadata.value_or(media_session::MediaMetadata());
533   base::string16 source_title =
534       session_metadata.source_title.empty()
535           ? message_center::MessageCenter::Get()->GetSystemNotificationAppName()
536           : session_metadata.source_title;
537   header_row_->SetAppName(source_title);
538 
539   title_label_->SetText(session_metadata.title);
540   artist_label_->SetText(session_metadata.artist);
541 
542   accessible_name_ =
543       media_message_center::GetAccessibleNameFromMetadata(session_metadata);
544 }
545 
MediaSessionActionsChanged(const std::vector<MediaSessionAction> & actions)546 void LockScreenMediaControlsView::MediaSessionActionsChanged(
547     const std::vector<MediaSessionAction>& actions) {
548   if (hide_controls_timer_->IsRunning())
549     return;
550 
551   enabled_actions_ =
552       base::flat_set<MediaSessionAction>(actions.begin(), actions.end());
553 
554   UpdateActionButtonsVisibility();
555 }
556 
MediaSessionChanged(const base::Optional<base::UnguessableToken> & request_id)557 void LockScreenMediaControlsView::MediaSessionChanged(
558     const base::Optional<base::UnguessableToken>& request_id) {
559   if (!media_session_id_.has_value()) {
560     media_session_id_ = request_id;
561     return;
562   }
563 
564   // If |media_session_id_| resumed while waiting, don't hide the controls.
565   if (hide_controls_timer_->IsRunning() && request_id == media_session_id_)
566     hide_controls_timer_->Stop();
567 
568   // If this session is different than the previous one, wait to see if the
569   // previous one resumes before hiding the controls.
570   if (request_id == media_session_id_)
571     return;
572 
573   hide_controls_timer_->Start(
574       FROM_HERE, kNextMediaDelay,
575       base::BindOnce(&LockScreenMediaControlsView::Hide, base::Unretained(this),
576                      HideReason::kSessionChanged));
577 }
578 
MediaSessionPositionChanged(const base::Optional<media_session::MediaPosition> & position)579 void LockScreenMediaControlsView::MediaSessionPositionChanged(
580     const base::Optional<media_session::MediaPosition>& position) {
581   if (hide_controls_timer_->IsRunning())
582     return;
583 
584   position_ = position;
585 
586   if (!position.has_value()) {
587     if (progress_->GetVisible()) {
588       progress_->SetVisible(false);
589       Layout();
590     }
591     return;
592   }
593 
594   progress_->UpdateProgress(*position);
595 
596   if (!progress_->GetVisible()) {
597     progress_->SetVisible(true);
598     Layout();
599   }
600 }
601 
MediaControllerImageChanged(media_session::mojom::MediaSessionImageType type,const SkBitmap & bitmap)602 void LockScreenMediaControlsView::MediaControllerImageChanged(
603     media_session::mojom::MediaSessionImageType type,
604     const SkBitmap& bitmap) {
605   if (hide_controls_timer_->IsRunning())
606     return;
607 
608   // Convert the bitmap to kN32_SkColorType if necessary.
609   SkBitmap converted_bitmap;
610   if (bitmap.colorType() == kN32_SkColorType) {
611     converted_bitmap = bitmap;
612   } else {
613     SkImageInfo info = bitmap.info().makeColorType(kN32_SkColorType);
614     if (converted_bitmap.tryAllocPixels(info)) {
615       bitmap.readPixels(info, converted_bitmap.getPixels(),
616                         converted_bitmap.rowBytes(), 0, 0);
617     }
618   }
619 
620   switch (type) {
621     case media_session::mojom::MediaSessionImageType::kArtwork: {
622       base::Optional<gfx::ImageSkia> session_artwork;
623       if (!converted_bitmap.empty())
624         session_artwork = gfx::ImageSkia::CreateFrom1xBitmap(converted_bitmap);
625       SetArtwork(session_artwork);
626       break;
627     }
628     case media_session::mojom::MediaSessionImageType::kSourceIcon: {
629       gfx::ImageSkia session_icon =
630           gfx::ImageSkia::CreateFrom1xBitmap(converted_bitmap);
631       if (session_icon.isNull()) {
632         session_icon =
633             gfx::CreateVectorIcon(message_center::kProductIcon,
634                                   kDesiredSourceIconSize, gfx::kChromeIconGrey);
635       }
636       header_row_->SetAppIcon(session_icon);
637     }
638   }
639 }
640 
OnImplicitAnimationsCompleted()641 void LockScreenMediaControlsView::OnImplicitAnimationsCompleted() {
642   Dismiss();
643 }
644 
OnGestureEvent(ui::GestureEvent * event)645 void LockScreenMediaControlsView::OnGestureEvent(ui::GestureEvent* event) {
646   gfx::Point point_in_screen = event->location();
647   ConvertPointToScreen(this, &point_in_screen);
648 
649   switch (event->type()) {
650     case ui::ET_SCROLL_FLING_START:
651     case ui::ET_GESTURE_SCROLL_BEGIN: {
652       if (is_in_drag_)
653         break;
654 
655       initial_drag_point_ = point_in_screen;
656       is_in_drag_ = true;
657       event->SetHandled();
658       break;
659     }
660     case ui::ET_GESTURE_SCROLL_UPDATE: {
661       last_fling_velocity_ = event->details().scroll_x();
662       UpdateDrag(point_in_screen);
663       event->SetHandled();
664       break;
665     }
666     case ui::ET_GESTURE_END: {
667       if (!is_in_drag_)
668         break;
669 
670       EndDrag();
671       event->SetHandled();
672       break;
673     }
674     default:
675       break;
676   }
677 }
678 
OnSuspend()679 void LockScreenMediaControlsView::OnSuspend() {
680   Hide(HideReason::kDeviceSleep);
681 }
682 
ButtonPressed(media_session::mojom::MediaSessionAction action)683 void LockScreenMediaControlsView::ButtonPressed(
684     media_session::mojom::MediaSessionAction action) {
685   if (base::Contains(enabled_actions_, action) &&
686       media_session_id_.has_value()) {
687     base::UmaHistogramEnumeration(kMediaControlsUserActionHistogramName,
688                                   action);
689     media_session::PerformMediaSessionAction(action, media_controller_remote_);
690   }
691 }
692 
FlushForTesting()693 void LockScreenMediaControlsView::FlushForTesting() {
694   media_controller_remote_.FlushForTesting();
695 }
696 
UpdateActionButtonsVisibility()697 void LockScreenMediaControlsView::UpdateActionButtonsVisibility() {
698   base::flat_set<MediaSessionAction> ignored_actions = {
699       media_message_center::GetPlayPauseIgnoredAction(
700           media_message_center::GetActionFromButtonTag(*play_pause_button_))};
701 
702   base::flat_set<MediaSessionAction> visible_actions =
703       media_message_center::GetTopVisibleActions(enabled_actions_,
704                                                  ignored_actions, kMaxActions);
705 
706   bool should_invalidate = false;
707   for (views::Button* action_button : media_action_buttons_) {
708     bool should_show = base::Contains(
709         visible_actions,
710         media_message_center::GetActionFromButtonTag(*action_button));
711     should_invalidate |= should_show != action_button->GetVisible();
712 
713     action_button->SetVisible(should_show);
714   }
715 
716   if (should_invalidate)
717     button_row_->InvalidateLayout();
718 }
719 
SetIsPlaying(bool playing)720 void LockScreenMediaControlsView::SetIsPlaying(bool playing) {
721   if (playing) {
722     play_pause_button_->SetAction(
723         MediaSessionAction::kPause,
724         l10n_util::GetStringUTF16(
725             IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACTION_PAUSE));
726   } else {
727     play_pause_button_->SetAction(
728         MediaSessionAction::kPlay,
729         l10n_util::GetStringUTF16(
730             IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACTION_PLAY));
731   }
732 
733   UpdateActionButtonsVisibility();
734 }
735 
SeekTo(double seek_progress)736 void LockScreenMediaControlsView::SeekTo(double seek_progress) {
737   DCHECK(position_.has_value());
738 
739   media_controller_remote_->SeekTo(seek_progress * position_->duration());
740 
741   base::UmaHistogramEnumeration(kMediaControlsUserActionHistogramName,
742                                 MediaSessionAction::kSeekTo);
743 }
744 
Hide(HideReason reason)745 void LockScreenMediaControlsView::Hide(HideReason reason) {
746   if (!hide_reason_ && GetVisible())
747     hide_reason_ = reason;
748 
749   hide_media_controls_.Run();
750 }
751 
HideArtwork()752 void LockScreenMediaControlsView::HideArtwork() {
753   session_artwork_->SetVisible(false);
754   session_artwork_->SetImage(nullptr);
755   session_artwork_->InvalidateLayout();
756 }
757 
SetShown(Shown shown)758 void LockScreenMediaControlsView::SetShown(Shown shown) {
759   if (shown_ == shown)
760     return;
761 
762   shown_ = shown;
763 
764   base::UmaHistogramEnumeration(kMediaControlsShownHistogramName, shown);
765 
766   if (shown == Shown::kShown) {
767     show_media_controls_.Run();
768   } else {
769     hide_media_controls_.Run();
770   }
771 }
772 
Dismiss()773 void LockScreenMediaControlsView::Dismiss() {
774   media_controller_remote_->Stop();
775 
776   base::UmaHistogramEnumeration(kMediaControlsUserActionHistogramName,
777                                 MediaSessionAction::kStop);
778 
779   Hide(HideReason::kDismissedByUser);
780 }
781 
SetArtwork(base::Optional<gfx::ImageSkia> img)782 void LockScreenMediaControlsView::SetArtwork(
783     base::Optional<gfx::ImageSkia> img) {
784   if (!img.has_value()) {
785     if (!session_artwork_->GetVisible() || hide_artwork_timer_->IsRunning())
786       return;
787 
788     hide_artwork_timer_->Start(
789         FROM_HERE, kNextMediaDelay,
790         base::BindOnce(&LockScreenMediaControlsView::HideArtwork,
791                        base::Unretained(this)));
792     return;
793   }
794 
795   if (hide_artwork_timer_->IsRunning())
796     hide_artwork_timer_->Stop();
797 
798   session_artwork_->SetVisible(true);
799   session_artwork_->SetImageSize(
800       ScaleSizeToFitView(img->size(), session_artwork_->GetPreferredSize()));
801   session_artwork_->SetImage(*img);
802 
803   Layout();
804   session_artwork_->SetClipPath(GetArtworkClipPath());
805 }
806 
GetArtworkClipPath() const807 SkPath LockScreenMediaControlsView::GetArtworkClipPath() const {
808   SkPath path;
809   path.addRoundRect(gfx::RectToSkRect(session_artwork_->GetImageBounds()),
810                     kArtworkCornerRadius, kArtworkCornerRadius);
811   return path;
812 }
813 
UpdateDrag(const gfx::Point & location_in_screen)814 void LockScreenMediaControlsView::UpdateDrag(
815     const gfx::Point& location_in_screen) {
816   is_in_drag_ = true;
817   int drag_delta = location_in_screen.x() - initial_drag_point_.x();
818 
819   // Don't let the user drag |contents_view_| below the view area.
820   if (contents_view_->bounds().x() + drag_delta <= GetLocalBounds().x()) {
821     return;
822   }
823 
824   gfx::Transform transform;
825   transform.Translate(drag_delta, 0);
826   contents_view_->layer()->SetTransform(transform);
827   UpdateOpacity();
828 }
829 
EndDrag()830 void LockScreenMediaControlsView::EndDrag() {
831   is_in_drag_ = false;
832 
833   int threshold = GetBoundsInScreen().right() - kDistanceDismissalThreshold;
834 
835   // If the user releases the drag with velocity over the threshold or drags
836   // |contents_view_| past the distance threshold, dismiss the controls.
837   if (last_fling_velocity_ >= kDragVelocityThreshold ||
838       (contents_view_->GetBoundsInScreen().x() >= threshold &&
839        last_fling_velocity_ < 1)) {
840     RunHideControlsAnimation();
841     return;
842   }
843 
844   RunResetControlsAnimation();
845 }
846 
UpdateOpacity()847 void LockScreenMediaControlsView::UpdateOpacity() {
848   float progress =
849       GetBoundsInScreen().x() /
850       static_cast<float>(contents_view_->GetBoundsInScreen().right());
851   contents_view_->layer()->SetOpacity(progress);
852 }
853 
RunHideControlsAnimation()854 void LockScreenMediaControlsView::RunHideControlsAnimation() {
855   ui::ScopedLayerAnimationSettings animation(
856       contents_view_->layer()->GetAnimator());
857   animation.AddObserver(this);
858   animation.SetPreemptionStrategy(
859       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
860   animation.SetTransitionDuration(kAnimationDuration);
861 
862   gfx::Transform transform;
863   transform.Translate(GetBoundsInScreen().right(), 0);
864   contents_view_->layer()->SetTransform(transform);
865   contents_view_->layer()->SetOpacity(0);
866 }
867 
RunResetControlsAnimation()868 void LockScreenMediaControlsView::RunResetControlsAnimation() {
869   ui::ScopedLayerAnimationSettings animation(
870       contents_view_->layer()->GetAnimator());
871   animation.SetPreemptionStrategy(
872       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
873   animation.SetTransitionDuration(kAnimationDuration);
874 
875   contents_view_->layer()->SetTransform(gfx::Transform());
876   contents_view_->layer()->SetOpacity(1);
877 }
878 
879 BEGIN_METADATA(LockScreenMediaControlsView, views::View)
880 END_METADATA
881 
882 }  // namespace ash
883