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