Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ash / system / cast / tray_cast.cc
blob33ff8f191127472045eb2586a09c9960f7550c3f
1 // Copyright 2015 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.
5 #include "ash/system/cast/tray_cast.h"
7 #include "ash/session/session_state_delegate.h"
8 #include "ash/shelf/shelf_types.h"
9 #include "ash/shell.h"
10 #include "ash/system/chromeos/screen_security/screen_tray_item.h"
11 #include "ash/system/tray/fixed_sized_image_view.h"
12 #include "ash/system/tray/fixed_sized_scroll_view.h"
13 #include "ash/system/tray/hover_highlight_view.h"
14 #include "ash/system/tray/system_tray.h"
15 #include "ash/system/tray/system_tray_delegate.h"
16 #include "ash/system/tray/system_tray_notifier.h"
17 #include "ash/system/tray/throbber_view.h"
18 #include "ash/system/tray/tray_constants.h"
19 #include "ash/system/tray/tray_details_view.h"
20 #include "ash/system/tray/tray_item_more.h"
21 #include "ash/system/tray/tray_item_view.h"
22 #include "ash/system/tray/tray_popup_label_button.h"
23 #include "base/bind.h"
24 #include "grit/ash_resources.h"
25 #include "grit/ash_strings.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/image/image.h"
29 #include "ui/views/controls/button/button.h"
30 #include "ui/views/controls/image_view.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/layout/box_layout.h"
33 #include "ui/views/layout/fill_layout.h"
35 namespace ash {
37 namespace {
38 const int kStopButtonRightPadding = 18;
39 } // namespace
41 namespace tray {
43 // This view is displayed in the system tray when the cast extension is active.
44 // It asks the user if they want to cast the desktop. If they click on the
45 // chevron, then a detail view will replace this view where the user will
46 // actually pick the cast receiver.
47 class CastSelectDefaultView : public TrayItemMore {
48 public:
49 CastSelectDefaultView(SystemTrayItem* owner,
50 CastConfigDelegate* cast_config_delegate,
51 bool show_more);
52 ~CastSelectDefaultView() override;
54 // Updates the label based on the current set of receivers (if there are or
55 // are not any available receivers).
56 void UpdateLabel();
58 private:
59 void UpdateLabelCallback(
60 const CastConfigDelegate::ReceiversAndActivites& receivers_activities);
62 CastConfigDelegate* cast_config_delegate_;
63 base::WeakPtrFactory<CastSelectDefaultView> weak_ptr_factory_;
64 DISALLOW_COPY_AND_ASSIGN(CastSelectDefaultView);
67 CastSelectDefaultView::CastSelectDefaultView(
68 SystemTrayItem* owner,
69 CastConfigDelegate* cast_config_delegate,
70 bool show_more)
71 : TrayItemMore(owner, show_more),
72 cast_config_delegate_(cast_config_delegate),
73 weak_ptr_factory_(this) {
74 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
75 SetImage(rb.GetImageNamed(IDR_AURA_UBER_TRAY_CAST).ToImageSkia());
77 // We first set a default label before we actually know what the label will
78 // be, because it could take awhile before UpdateLabel() actually applies
79 // the correct label.
80 SetLabel(rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_NO_DEVICE));
81 UpdateLabel();
84 CastSelectDefaultView::~CastSelectDefaultView() {
87 void CastSelectDefaultView::UpdateLabelCallback(
88 const CastConfigDelegate::ReceiversAndActivites& receivers_activities) {
89 // The label needs to reflect if there are no cast receivers
90 const base::string16 label =
91 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
92 receivers_activities.empty() ? IDS_ASH_STATUS_TRAY_CAST_NO_DEVICE
93 : IDS_ASH_STATUS_TRAY_CAST_DESKTOP);
94 SetLabel(label);
95 SetAccessibleName(label);
96 SetVisible(true);
99 void CastSelectDefaultView::UpdateLabel() {
100 if (cast_config_delegate_ == nullptr ||
101 cast_config_delegate_->HasCastExtension() == false)
102 return;
104 cast_config_delegate_->GetReceiversAndActivities(
105 base::Bind(&CastSelectDefaultView::UpdateLabelCallback,
106 weak_ptr_factory_.GetWeakPtr()));
109 // This view is displayed when the screen is actively being casted; it allows
110 // the user to easily stop casting. It fully replaces the
111 // |CastSelectDefaultView| view inside of the |CastDuplexView|.
112 class CastCastView : public views::View, public views::ButtonListener {
113 public:
114 explicit CastCastView(CastConfigDelegate* cast_config_delegate);
115 ~CastCastView() override;
117 // Updates the label for the stop view to include information about the
118 // current device that is being casted.
119 void UpdateLabel();
121 private:
122 void UpdateLabelCallback(
123 const CastConfigDelegate::ReceiversAndActivites& receivers_activities);
125 // Overridden from views::View.
126 int GetHeightForWidth(int width) const override;
127 void Layout() override;
129 // Overridden from views::ButtonListener.
130 void ButtonPressed(views::Button* sender, const ui::Event& event) override;
132 CastConfigDelegate* cast_config_delegate_;
133 views::ImageView* icon_;
134 views::Label* label_;
135 TrayPopupLabelButton* stop_button_;
136 base::WeakPtrFactory<CastCastView> weak_ptr_factory_;
138 DISALLOW_COPY_AND_ASSIGN(CastCastView);
141 CastCastView::CastCastView(CastConfigDelegate* cast_config_delegate)
142 : cast_config_delegate_(cast_config_delegate), weak_ptr_factory_(this) {
143 // We will initialize the primary tray view which shows a stop button here.
145 set_background(views::Background::CreateSolidBackground(kBackgroundColor));
146 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
147 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
148 kTrayPopupPaddingHorizontal, 0,
149 kTrayPopupPaddingBetweenItems));
150 icon_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
151 icon_->SetImage(
152 bundle.GetImageNamed(IDR_AURA_UBER_TRAY_CAST_ENABLED).ToImageSkia());
153 AddChildView(icon_);
155 // The label which describes both what we are casting (ie, the desktop) and
156 // where we are casting it to.
157 label_ = new views::Label;
158 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
159 label_->SetMultiLine(true);
160 label_->SetText(
161 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_CAST_UNKNOWN));
162 AddChildView(label_);
164 // Add the stop bottom on the far-right. We customize how this stop button is
165 // displayed inside of |Layout()|.
166 base::string16 stop_button_text =
167 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
168 IDS_ASH_STATUS_TRAY_CAST_STOP);
169 stop_button_ = new TrayPopupLabelButton(this, stop_button_text);
170 AddChildView(stop_button_);
172 UpdateLabel();
175 CastCastView::~CastCastView() {
178 int CastCastView::GetHeightForWidth(int width) const {
179 // We are reusing the cached label_->bounds() calculation which was
180 // done inside of Layout(). Due to the way this object is initialized,
181 // Layout() will always get initially invoked with the dummy text
182 // (which will compute the proper label width) and then when we know
183 // the cast receiver we will update the label text, which will cause
184 // this method to get invoked.
185 return std::max(views::View::GetHeightForWidth(width),
186 GetInsets().height() +
187 label_->GetHeightForWidth(label_->bounds().width()));
190 void CastCastView::Layout() {
191 views::View::Layout();
193 // Give the stop button the space it requests.
194 gfx::Size stop_size = stop_button_->GetPreferredSize();
195 gfx::Rect stop_bounds(stop_size);
196 stop_bounds.set_x(width() - stop_size.width() - kStopButtonRightPadding);
197 stop_bounds.set_y((height() - stop_size.height()) / 2);
198 stop_button_->SetBoundsRect(stop_bounds);
200 // Adjust the label's bounds in case it got cut off by |stop_button_|.
201 if (label_->bounds().Intersects(stop_button_->bounds())) {
202 gfx::Rect label_bounds = label_->bounds();
203 label_bounds.set_width(stop_button_->x() - kTrayPopupPaddingBetweenItems -
204 label_->x());
205 label_->SetBoundsRect(label_bounds);
209 void CastCastView::UpdateLabel() {
210 if (cast_config_delegate_ == nullptr ||
211 cast_config_delegate_->HasCastExtension() == false)
212 return;
214 cast_config_delegate_->GetReceiversAndActivities(base::Bind(
215 &CastCastView::UpdateLabelCallback, weak_ptr_factory_.GetWeakPtr()));
218 void CastCastView::UpdateLabelCallback(
219 const CastConfigDelegate::ReceiversAndActivites& receivers_activities) {
220 for (auto& i : receivers_activities) {
221 const CastConfigDelegate::Receiver receiver = i.second.receiver;
222 const CastConfigDelegate::Activity activity = i.second.activity;
223 if (!activity.id.empty()) {
224 // We want to display different labels inside of the title depending on
225 // what we are actually casting - either the desktop, a tab, or a fallback
226 // that catches everything else (ie, an extension tab).
227 if (activity.tab_id == CastConfigDelegate::Activity::TabId::DESKTOP) {
228 label_->SetText(l10n_util::GetStringFUTF16(
229 IDS_ASH_STATUS_TRAY_CAST_CAST_DESKTOP, receiver.name));
230 } else if (activity.tab_id >= 0) {
231 label_->SetText(l10n_util::GetStringFUTF16(
232 IDS_ASH_STATUS_TRAY_CAST_CAST_TAB, activity.title, receiver.name));
233 } else {
234 label_->SetText(
235 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_CAST_UNKNOWN));
238 PreferredSizeChanged();
239 Layout();
240 break;
245 void CastCastView::ButtonPressed(views::Button* sender,
246 const ui::Event& event) {
247 DCHECK(sender == stop_button_);
248 cast_config_delegate_->StopCasting();
249 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
250 ash::UMA_STATUS_AREA_CAST_STOP_CAST);
253 // This view by itself does very little. It acts as a front-end for managing
254 // which of the two child views (|CastSelectDefaultView| and |CastCastView|)
255 // is active.
256 class CastDuplexView : public views::View {
257 public:
258 CastDuplexView(SystemTrayItem* owner,
259 CastConfigDelegate* config_delegate,
260 bool show_more);
261 ~CastDuplexView() override;
263 // Activate either the casting or select view.
264 void ActivateCastView();
265 void ActivateSelectView();
267 CastSelectDefaultView* select_view() { return select_view_; }
268 CastCastView* cast_view() { return cast_view_; }
270 private:
271 // Overridden from views::View.
272 void ChildPreferredSizeChanged(views::View* child) override;
273 void Layout() override;
275 // Only one of |select_view_| or |cast_view_| will be displayed at any given
276 // time. This will return the view is being displayed.
277 views::View* ActiveChildView();
279 CastSelectDefaultView* select_view_;
280 CastCastView* cast_view_;
282 DISALLOW_COPY_AND_ASSIGN(CastDuplexView);
285 CastDuplexView::CastDuplexView(SystemTrayItem* owner,
286 CastConfigDelegate* config_delegate,
287 bool show_more) {
288 select_view_ = new CastSelectDefaultView(owner, config_delegate, show_more);
289 cast_view_ = new CastCastView(config_delegate);
290 SetLayoutManager(new views::FillLayout());
292 ActivateSelectView();
295 CastDuplexView::~CastDuplexView() {
296 RemoveChildView(ActiveChildView());
297 delete select_view_;
298 delete cast_view_;
301 void CastDuplexView::ActivateCastView() {
302 if (ActiveChildView() == cast_view_)
303 return;
305 RemoveChildView(select_view_);
306 AddChildView(cast_view_);
307 InvalidateLayout();
310 void CastDuplexView::ActivateSelectView() {
311 if (ActiveChildView() == select_view_)
312 return;
314 RemoveChildView(cast_view_);
315 AddChildView(select_view_);
316 InvalidateLayout();
319 void CastDuplexView::ChildPreferredSizeChanged(views::View* child) {
320 PreferredSizeChanged();
323 void CastDuplexView::Layout() {
324 views::View::Layout();
326 select_view_->SetBoundsRect(GetContentsBounds());
327 cast_view_->SetBoundsRect(GetContentsBounds());
330 views::View* CastDuplexView::ActiveChildView() {
331 if (cast_view_->parent() == this)
332 return cast_view_;
333 if (select_view_->parent() == this)
334 return select_view_;
335 return nullptr;
338 // Exposes an icon in the tray. |TrayCast| manages the visiblity of this.
339 class CastTrayView : public TrayItemView {
340 public:
341 CastTrayView(SystemTrayItem* tray_item);
342 ~CastTrayView() override;
344 // Called when the tray alignment changes so that the icon can recenter
345 // itself.
346 void UpdateAlignment(ShelfAlignment alignment);
348 private:
349 DISALLOW_COPY_AND_ASSIGN(CastTrayView);
352 CastTrayView::CastTrayView(SystemTrayItem* tray_item)
353 : TrayItemView(tray_item) {
354 CreateImageView();
356 image_view()->SetImage(ui::ResourceBundle::GetSharedInstance()
357 .GetImageNamed(IDR_AURA_UBER_TRAY_SCREENSHARE)
358 .ToImageSkia());
361 CastTrayView::~CastTrayView() {
364 void CastTrayView::UpdateAlignment(ShelfAlignment alignment) {
365 // Center the item dependent on the orientation of the shelf.
366 views::BoxLayout::Orientation layout = views::BoxLayout::kHorizontal;
367 switch (alignment) {
368 case ash::SHELF_ALIGNMENT_BOTTOM:
369 case ash::SHELF_ALIGNMENT_TOP:
370 layout = views::BoxLayout::kHorizontal;
371 break;
372 case ash::SHELF_ALIGNMENT_LEFT:
373 case ash::SHELF_ALIGNMENT_RIGHT:
374 layout = views::BoxLayout::kVertical;
375 break;
377 SetLayoutManager(new views::BoxLayout(layout, 0, 0, 0));
378 Layout();
381 // This view displays a list of cast receivers that can be clicked on and casted
382 // to. It is activated by clicking on the chevron inside of
383 // |CastSelectDefaultView|.
384 class CastDetailedView : public TrayDetailsView, public ViewClickListener {
385 public:
386 CastDetailedView(SystemTrayItem* owner,
387 CastConfigDelegate* cast_config_delegate,
388 user::LoginStatus login);
389 ~CastDetailedView() override;
391 private:
392 void CreateItems();
394 void UpdateReceiverList();
395 void UpdateReceiverListCallback(
396 const CastConfigDelegate::ReceiversAndActivites&
397 new_receivers_and_activities);
398 void UpdateReceiverListFromCachedData();
399 views::View* AddToReceiverList(
400 const CastConfigDelegate::ReceiverAndActivity& receiverActivity);
402 void AppendSettingsEntries();
403 void AppendHeaderEntry();
405 // Overridden from ViewClickListener.
406 void OnViewClicked(views::View* sender) override;
408 CastConfigDelegate* cast_config_delegate_;
409 user::LoginStatus login_;
410 views::View* options_ = nullptr;
411 CastConfigDelegate::ReceiversAndActivites receivers_and_activities_;
412 // A mapping from the view pointer to the associated activity id
413 std::map<views::View*, std::string> receiver_activity_map_;
414 base::WeakPtrFactory<CastDetailedView> weak_ptr_factory_;
416 DISALLOW_COPY_AND_ASSIGN(CastDetailedView);
419 CastDetailedView::CastDetailedView(SystemTrayItem* owner,
420 CastConfigDelegate* cast_config_delegate,
421 user::LoginStatus login)
422 : TrayDetailsView(owner),
423 cast_config_delegate_(cast_config_delegate),
424 login_(login),
425 weak_ptr_factory_(this) {
426 CreateItems();
427 UpdateReceiverList();
430 CastDetailedView::~CastDetailedView() {
433 void CastDetailedView::CreateItems() {
434 CreateScrollableList();
435 AppendSettingsEntries();
436 AppendHeaderEntry();
439 void CastDetailedView::UpdateReceiverList() {
440 cast_config_delegate_->GetReceiversAndActivities(
441 base::Bind(&CastDetailedView::UpdateReceiverListCallback,
442 weak_ptr_factory_.GetWeakPtr()));
445 void CastDetailedView::UpdateReceiverListCallback(
446 const CastConfigDelegate::ReceiversAndActivites&
447 new_receivers_and_activities) {
448 // Add/update existing.
449 for (auto i = new_receivers_and_activities.begin();
450 i != new_receivers_and_activities.end(); ++i) {
451 receivers_and_activities_[i->first] = i->second;
453 // Remove non-existent.
454 for (auto i = receivers_and_activities_.begin();
455 i != receivers_and_activities_.end(); ++i) {
456 if (new_receivers_and_activities.count(i->first) == 0)
457 receivers_and_activities_.erase(i->first);
460 // Update UI.
461 UpdateReceiverListFromCachedData();
462 Layout();
465 void CastDetailedView::UpdateReceiverListFromCachedData() {
466 // Remove all of the existing views.
467 receiver_activity_map_.clear();
468 scroll_content()->RemoveAllChildViews(true);
470 // Add a view for each receiver.
471 for (auto& it : receivers_and_activities_) {
472 const CastConfigDelegate::ReceiverAndActivity& receiver_activity =
473 it.second;
474 views::View* container = AddToReceiverList(receiver_activity);
475 receiver_activity_map_[container] = it.first;
478 scroll_content()->SizeToPreferredSize();
479 static_cast<views::View*>(scroller())->Layout();
482 views::View* CastDetailedView::AddToReceiverList(
483 const CastConfigDelegate::ReceiverAndActivity& receiverActivity) {
484 HoverHighlightView* container = new HoverHighlightView(this);
486 const gfx::ImageSkia* image =
487 ui::ResourceBundle::GetSharedInstance()
488 .GetImageNamed(IDR_AURA_UBER_TRAY_CAST_DEVICE_ICON)
489 .ToImageSkia();
490 const base::string16& name = receiverActivity.receiver.name;
491 container->AddIndentedIconAndLabel(*image, name, false);
493 scroll_content()->AddChildView(container);
494 return container;
497 void CastDetailedView::AppendSettingsEntries() {
498 // Settings requires a browser window, hide it for non logged in user.
499 const bool userAddingRunning = Shell::GetInstance()
500 ->session_state_delegate()
501 ->IsInSecondaryLoginScreen();
503 if (login_ == user::LOGGED_IN_NONE || login_ == user::LOGGED_IN_LOCKED ||
504 userAddingRunning)
505 return;
507 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
508 HoverHighlightView* container = new HoverHighlightView(this);
509 container->AddLabel(rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_OPTIONS),
510 gfx::ALIGN_LEFT, false /* highlight */);
512 AddChildView(container);
513 options_ = container;
516 void CastDetailedView::AppendHeaderEntry() {
517 CreateSpecialRow(IDS_ASH_STATUS_TRAY_CAST, this);
520 void CastDetailedView::OnViewClicked(views::View* sender) {
521 if (sender == footer()->content()) {
522 TransitionToDefaultView();
523 } else if (sender == options_) {
524 cast_config_delegate_->LaunchCastOptions();
525 } else {
526 // Find the receiver we are going to cast to
527 auto it = receiver_activity_map_.find(sender);
528 if (it != receiver_activity_map_.end()) {
529 cast_config_delegate_->CastToReceiver(it->second);
530 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
531 ash::UMA_STATUS_AREA_DETAILED_CAST_VIEW_LAUNCH_CAST);
536 } // namespace tray
538 TrayCast::TrayCast(SystemTray* system_tray)
539 : SystemTrayItem(system_tray),
540 cast_config_delegate_(ash::Shell::GetInstance()
541 ->system_tray_delegate()
542 ->GetCastConfigDelegate()),
543 weak_ptr_factory_(this) {
544 Shell::GetInstance()->AddShellObserver(this);
547 TrayCast::~TrayCast() {
548 Shell::GetInstance()->RemoveShellObserver(this);
551 views::View* TrayCast::CreateTrayView(user::LoginStatus status) {
552 CHECK(tray_ == nullptr);
553 tray_ = new tray::CastTrayView(this);
554 tray_->SetVisible(is_casting_);
555 return tray_;
558 views::View* TrayCast::CreateDefaultView(user::LoginStatus status) {
559 CHECK(default_ == nullptr);
560 default_ = new tray::CastDuplexView(this, cast_config_delegate_,
561 status != user::LOGGED_IN_LOCKED);
562 UpdatePrimaryView();
563 return default_;
566 views::View* TrayCast::CreateDetailedView(user::LoginStatus status) {
567 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
568 ash::UMA_STATUS_AREA_DETAILED_CAST_VIEW);
569 CHECK(detailed_ == nullptr);
570 detailed_ = new tray::CastDetailedView(this, cast_config_delegate_, status);
571 return detailed_;
574 void TrayCast::DestroyTrayView() {
575 tray_ = nullptr;
578 void TrayCast::DestroyDefaultView() {
579 default_ = nullptr;
582 void TrayCast::DestroyDetailedView() {
583 detailed_ = nullptr;
586 bool TrayCast::HasCastExtension() {
587 return cast_config_delegate_ != nullptr &&
588 cast_config_delegate_->HasCastExtension();
591 void TrayCast::UpdateCachedReceiverState(
592 const CastConfigDelegate::ReceiversAndActivites& receivers_activities) {
593 has_cast_receivers_ = !receivers_activities.empty();
594 if (default_)
595 default_->SetVisible(has_cast_receivers_);
598 void TrayCast::UpdatePrimaryView() {
599 if (HasCastExtension()) {
600 if (default_) {
601 if (is_casting_) {
602 default_->ActivateCastView();
603 } else {
604 default_->ActivateSelectView();
606 // We only want to show the select view if we have a receiver we can
607 // cast to. To prevent showing the tray item and then hiding it some
608 // short time after, we cache if we have any receivers. We set our
609 // default visibility to true if we do have a receiver, false otherwise.
610 default_->SetVisible(has_cast_receivers_);
611 cast_config_delegate_->GetReceiversAndActivities(
612 base::Bind(&TrayCast::UpdateCachedReceiverState,
613 weak_ptr_factory_.GetWeakPtr()));
617 if (tray_)
618 tray_->SetVisible(is_casting_);
619 } else {
620 if (default_)
621 default_->SetVisible(false);
622 if (tray_)
623 tray_->SetVisible(false);
627 void TrayCast::OnCastingSessionStartedOrStopped(bool started) {
628 is_casting_ = started;
629 UpdatePrimaryView();
632 void TrayCast::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
633 if (tray_)
634 tray_->UpdateAlignment(alignment);
637 } // namespace ash