Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1398)

Side by Side Diff: ash/common/system/chromeos/cast/tray_cast.cc

Issue 2734653002: chromeos: Move files in //ash/common to //ash (Closed)
Patch Set: fix a11y tests, fix docs Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #include "ash/common/system/chromeos/cast/tray_cast.h"
6
7 #include <map>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "ash/common/cast_config_controller.h"
13 #include "ash/common/material_design/material_design_controller.h"
14 #include "ash/common/session/session_state_delegate.h"
15 #include "ash/common/shelf/wm_shelf_util.h"
16 #include "ash/common/system/chromeos/screen_security/screen_tray_item.h"
17 #include "ash/common/system/tray/fixed_sized_image_view.h"
18 #include "ash/common/system/tray/hover_highlight_view.h"
19 #include "ash/common/system/tray/system_tray.h"
20 #include "ash/common/system/tray/system_tray_delegate.h"
21 #include "ash/common/system/tray/throbber_view.h"
22 #include "ash/common/system/tray/tray_constants.h"
23 #include "ash/common/system/tray/tray_details_view.h"
24 #include "ash/common/system/tray/tray_item_more.h"
25 #include "ash/common/system/tray/tray_item_view.h"
26 #include "ash/common/system/tray/tray_utils.h"
27 #include "ash/common/wm_shell.h"
28 #include "ash/public/cpp/shelf_types.h"
29 #include "ash/public/interfaces/cast_config.mojom.h"
30 #include "ash/resources/grit/ash_resources.h"
31 #include "ash/resources/vector_icons/vector_icons.h"
32 #include "ash/strings/grit/ash_strings.h"
33 #include "base/bind.h"
34 #include "base/strings/utf_string_conversions.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "ui/gfx/image/image.h"
38 #include "ui/gfx/paint_vector_icon.h"
39 #include "ui/gfx/text_elider.h"
40 #include "ui/views/background.h"
41 #include "ui/views/border.h"
42 #include "ui/views/controls/button/button.h"
43 #include "ui/views/controls/image_view.h"
44 #include "ui/views/controls/label.h"
45 #include "ui/views/controls/scroll_view.h"
46 #include "ui/views/layout/box_layout.h"
47 #include "ui/views/layout/fill_layout.h"
48
49 namespace ash {
50
51 namespace {
52
53 const size_t kMaximumStatusStringLength = 100;
54
55 // Helper method to elide the given string to the maximum length. If a string is
56 // contains user-input and is displayed, we should elide it.
57 // TODO(jdufault): This does not properly trim unicode characters. We should
58 // implement this properly by using views::Label::SetElideBehavior(...). See
59 // crbug.com/532496.
60 base::string16 ElideString(const base::string16& text) {
61 base::string16 elided;
62 gfx::ElideString(text, kMaximumStatusStringLength, &elided);
63 return elided;
64 }
65
66 // Returns a vectorized version of the Cast icon. The icon's interior region is
67 // filled in if |is_casting| is true.
68 gfx::ImageSkia GetCastIconForSystemMenu(bool is_casting) {
69 return gfx::CreateVectorIcon(
70 kSystemMenuCastIcon, is_casting ? kMenuIconColor : SK_ColorTRANSPARENT);
71 }
72
73 } // namespace
74
75 namespace tray {
76
77 // This view is displayed in the system tray when the cast extension is active.
78 // It asks the user if they want to cast the desktop. If they click on the
79 // chevron, then a detail view will replace this view where the user will
80 // actually pick the cast receiver.
81 class CastSelectDefaultView : public TrayItemMore {
82 public:
83 explicit CastSelectDefaultView(SystemTrayItem* owner);
84 ~CastSelectDefaultView() override;
85
86 private:
87 DISALLOW_COPY_AND_ASSIGN(CastSelectDefaultView);
88 };
89
90 CastSelectDefaultView::CastSelectDefaultView(SystemTrayItem* owner)
91 : TrayItemMore(owner) {
92 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
93
94 // Update the image and label.
95 if (MaterialDesignController::IsSystemTrayMenuMaterial())
96 SetImage(GetCastIconForSystemMenu(false));
97 else
98 SetImage(*rb.GetImageNamed(IDR_AURA_UBER_TRAY_CAST).ToImageSkia());
99
100 base::string16 label =
101 rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_DESKTOP);
102 SetLabel(label);
103 SetAccessibleName(label);
104 }
105
106 CastSelectDefaultView::~CastSelectDefaultView() {}
107
108 // This view is displayed when the screen is actively being casted; it allows
109 // the user to easily stop casting. It fully replaces the
110 // |CastSelectDefaultView| view inside of the |CastDuplexView|.
111 class CastCastView : public ScreenStatusView {
112 public:
113 CastCastView();
114 ~CastCastView() override;
115
116 void StopCasting();
117
118 const std::string& displayed_route_id() const { return displayed_route_->id; }
119
120 // Updates the label for the stop view to include information about the
121 // current device that is being casted.
122 void UpdateLabel(const std::vector<mojom::SinkAndRoutePtr>& sinks_routes);
123
124 private:
125 // Overridden from views::ButtonListener.
126 void ButtonPressed(views::Button* sender, const ui::Event& event) override;
127
128 // The cast activity id that we are displaying. If the user stops a cast, we
129 // send this value to the config delegate so that we stop the right cast.
130 mojom::CastRoutePtr displayed_route_;
131
132 DISALLOW_COPY_AND_ASSIGN(CastCastView);
133 };
134
135 CastCastView::CastCastView()
136 : ScreenStatusView(
137 nullptr,
138 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_CAST_UNKNOWN),
139 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_STOP)) {
140 if (MaterialDesignController::IsSystemTrayMenuMaterial()) {
141 icon()->SetImage(GetCastIconForSystemMenu(true));
142 } else {
143 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
144 icon()->SetImage(
145 bundle.GetImageNamed(IDR_AURA_UBER_TRAY_CAST_ENABLED).ToImageSkia());
146 }
147 }
148
149 CastCastView::~CastCastView() {}
150
151 void CastCastView::StopCasting() {
152 WmShell::Get()->cast_config()->StopCasting(displayed_route_.Clone());
153 WmShell::Get()->RecordUserMetricsAction(UMA_STATUS_AREA_CAST_STOP_CAST);
154 }
155
156 void CastCastView::UpdateLabel(
157 const std::vector<mojom::SinkAndRoutePtr>& sinks_routes) {
158 for (auto& i : sinks_routes) {
159 const mojom::CastSinkPtr& sink = i->sink;
160 const mojom::CastRoutePtr& route = i->route;
161
162 // We only want to display casts that came from this machine, since on a
163 // busy network many other people could be casting.
164 if (!route->id.empty() && route->is_local_source) {
165 displayed_route_ = route.Clone();
166
167 // We want to display different labels inside of the title depending on
168 // what we are actually casting - either the desktop, a tab, or a fallback
169 // that catches everything else (ie, an extension tab).
170 switch (route->content_source) {
171 case ash::mojom::ContentSource::UNKNOWN:
172 label()->SetText(
173 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_CAST_UNKNOWN));
174 stop_button()->SetAccessibleName(l10n_util::GetStringUTF16(
175 IDS_ASH_STATUS_TRAY_CAST_CAST_UNKNOWN_ACCESSIBILITY_STOP));
176 break;
177 case ash::mojom::ContentSource::TAB:
178 label()->SetText(ElideString(l10n_util::GetStringFUTF16(
179 IDS_ASH_STATUS_TRAY_CAST_CAST_TAB,
180 base::UTF8ToUTF16(route->title), base::UTF8ToUTF16(sink->name))));
181 stop_button()->SetAccessibleName(
182 ElideString(l10n_util::GetStringFUTF16(
183 IDS_ASH_STATUS_TRAY_CAST_CAST_TAB_ACCESSIBILITY_STOP,
184 base::UTF8ToUTF16(route->title),
185 base::UTF8ToUTF16(sink->name))));
186 break;
187 case ash::mojom::ContentSource::DESKTOP:
188 label()->SetText(ElideString(
189 l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_CAST_CAST_DESKTOP,
190 base::UTF8ToUTF16(sink->name))));
191 stop_button()->SetAccessibleName(
192 ElideString(l10n_util::GetStringFUTF16(
193 IDS_ASH_STATUS_TRAY_CAST_CAST_DESKTOP_ACCESSIBILITY_STOP,
194 base::UTF8ToUTF16(sink->name))));
195 break;
196 }
197
198 PreferredSizeChanged();
199 Layout();
200 // Only need to update labels once.
201 break;
202 }
203 }
204 }
205
206 void CastCastView::ButtonPressed(views::Button* sender,
207 const ui::Event& event) {
208 StopCasting();
209 }
210
211 // This view by itself does very little. It acts as a front-end for managing
212 // which of the two child views (|CastSelectDefaultView| and |CastCastView|)
213 // is active.
214 class CastDuplexView : public views::View {
215 public:
216 CastDuplexView(SystemTrayItem* owner,
217 bool enabled,
218 const std::vector<mojom::SinkAndRoutePtr>& sinks_routes);
219 ~CastDuplexView() override;
220
221 // Activate either the casting or select view.
222 void ActivateCastView();
223 void ActivateSelectView();
224
225 CastSelectDefaultView* select_view() { return select_view_; }
226 CastCastView* cast_view() { return cast_view_; }
227
228 private:
229 // Overridden from views::View.
230 void ChildPreferredSizeChanged(views::View* child) override;
231 void Layout() override;
232
233 // Only one of |select_view_| or |cast_view_| will be displayed at any given
234 // time. This will return the view is being displayed.
235 views::View* ActiveChildView();
236
237 CastSelectDefaultView* select_view_;
238 CastCastView* cast_view_;
239
240 DISALLOW_COPY_AND_ASSIGN(CastDuplexView);
241 };
242
243 CastDuplexView::CastDuplexView(
244 SystemTrayItem* owner,
245 bool enabled,
246 const std::vector<mojom::SinkAndRoutePtr>& sinks_routes) {
247 select_view_ = new CastSelectDefaultView(owner);
248 select_view_->SetEnabled(enabled);
249 cast_view_ = new CastCastView();
250 cast_view_->UpdateLabel(sinks_routes);
251 SetLayoutManager(new views::FillLayout());
252
253 ActivateSelectView();
254 }
255
256 CastDuplexView::~CastDuplexView() {
257 RemoveChildView(ActiveChildView());
258 delete select_view_;
259 delete cast_view_;
260 }
261
262 void CastDuplexView::ActivateCastView() {
263 if (ActiveChildView() == cast_view_)
264 return;
265
266 RemoveChildView(select_view_);
267 AddChildView(cast_view_);
268 InvalidateLayout();
269 }
270
271 void CastDuplexView::ActivateSelectView() {
272 if (ActiveChildView() == select_view_)
273 return;
274
275 RemoveChildView(cast_view_);
276 AddChildView(select_view_);
277 InvalidateLayout();
278 }
279
280 void CastDuplexView::ChildPreferredSizeChanged(views::View* child) {
281 PreferredSizeChanged();
282 }
283
284 void CastDuplexView::Layout() {
285 views::View::Layout();
286
287 select_view_->SetBoundsRect(GetContentsBounds());
288 cast_view_->SetBoundsRect(GetContentsBounds());
289 }
290
291 views::View* CastDuplexView::ActiveChildView() {
292 if (cast_view_->parent() == this)
293 return cast_view_;
294 if (select_view_->parent() == this)
295 return select_view_;
296 return nullptr;
297 }
298
299 // Exposes an icon in the tray. |TrayCast| manages the visiblity of this.
300 class CastTrayView : public TrayItemView {
301 public:
302 explicit CastTrayView(SystemTrayItem* tray_item);
303 ~CastTrayView() override;
304
305 private:
306 DISALLOW_COPY_AND_ASSIGN(CastTrayView);
307 };
308
309 CastTrayView::CastTrayView(SystemTrayItem* tray_item)
310 : TrayItemView(tray_item) {
311 CreateImageView();
312 if (MaterialDesignController::UseMaterialDesignSystemIcons()) {
313 image_view()->SetImage(
314 gfx::CreateVectorIcon(kSystemTrayCastIcon, kTrayIconColor));
315 } else {
316 image_view()->SetImage(ui::ResourceBundle::GetSharedInstance()
317 .GetImageNamed(IDR_AURA_UBER_TRAY_SCREENSHARE)
318 .ToImageSkia());
319 }
320 }
321
322 CastTrayView::~CastTrayView() {}
323
324 // This view displays a list of cast receivers that can be clicked on and casted
325 // to. It is activated by clicking on the chevron inside of
326 // |CastSelectDefaultView|.
327 class CastDetailedView : public TrayDetailsView {
328 public:
329 CastDetailedView(SystemTrayItem* owner,
330 const std::vector<mojom::SinkAndRoutePtr>& sinks_and_routes);
331 ~CastDetailedView() override;
332
333 // Makes the detail view think the view associated with the given receiver_id
334 // was clicked. This will start a cast.
335 void SimulateViewClickedForTest(const std::string& receiver_id);
336
337 // Updates the list of available receivers.
338 void UpdateReceiverList(
339 const std::vector<mojom::SinkAndRoutePtr>& sinks_routes);
340
341 private:
342 void CreateItems();
343
344 void UpdateReceiverListFromCachedData();
345 views::View* AddToReceiverList(const mojom::SinkAndRoutePtr& sink_route);
346
347 // TrayDetailsView:
348 void HandleViewClicked(views::View* view) override;
349
350 // A mapping from the receiver id to the receiver/activity data.
351 std::map<std::string, ash::mojom::SinkAndRoutePtr> sinks_and_routes_;
352 // A mapping from the view pointer to the associated activity id.
353 std::map<views::View*, ash::mojom::CastSinkPtr> view_to_sink_map_;
354
355 DISALLOW_COPY_AND_ASSIGN(CastDetailedView);
356 };
357
358 CastDetailedView::CastDetailedView(
359 SystemTrayItem* owner,
360 const std::vector<mojom::SinkAndRoutePtr>& sinks_routes)
361 : TrayDetailsView(owner) {
362 CreateItems();
363 UpdateReceiverList(sinks_routes);
364 }
365
366 CastDetailedView::~CastDetailedView() {}
367
368 void CastDetailedView::SimulateViewClickedForTest(
369 const std::string& receiver_id) {
370 for (const auto& it : view_to_sink_map_) {
371 if (it.second->id == receiver_id) {
372 HandleViewClicked(it.first);
373 break;
374 }
375 }
376 }
377
378 void CastDetailedView::CreateItems() {
379 CreateScrollableList();
380 CreateTitleRow(IDS_ASH_STATUS_TRAY_CAST);
381 }
382
383 void CastDetailedView::UpdateReceiverList(
384 const std::vector<mojom::SinkAndRoutePtr>& sinks_routes) {
385 // Add/update existing.
386 for (const auto& it : sinks_routes)
387 sinks_and_routes_[it->sink->id] = it->Clone();
388
389 // Remove non-existent sinks. Removing an element invalidates all existing
390 // iterators.
391 auto i = sinks_and_routes_.begin();
392 while (i != sinks_and_routes_.end()) {
393 bool has_receiver = false;
394 for (auto& receiver : sinks_routes) {
395 if (i->first == receiver->sink->id)
396 has_receiver = true;
397 }
398
399 if (has_receiver)
400 ++i;
401 else
402 i = sinks_and_routes_.erase(i);
403 }
404
405 // Update UI.
406 UpdateReceiverListFromCachedData();
407 Layout();
408 }
409
410 void CastDetailedView::UpdateReceiverListFromCachedData() {
411 // Remove all of the existing views.
412 view_to_sink_map_.clear();
413 scroll_content()->RemoveAllChildViews(true);
414
415 // Add a view for each receiver.
416 for (auto& it : sinks_and_routes_) {
417 const ash::mojom::SinkAndRoutePtr& sink_route = it.second;
418 views::View* container = AddToReceiverList(sink_route);
419 view_to_sink_map_[container] = sink_route->sink.Clone();
420 }
421
422 scroll_content()->SizeToPreferredSize();
423 scroller()->Layout();
424 }
425
426 views::View* CastDetailedView::AddToReceiverList(
427 const ash::mojom::SinkAndRoutePtr& sink_route) {
428 const gfx::ImageSkia image =
429 MaterialDesignController::IsSystemTrayMenuMaterial()
430 ? gfx::CreateVectorIcon(kSystemMenuCastDeviceIcon, kMenuIconColor)
431 : *ui::ResourceBundle::GetSharedInstance()
432 .GetImageNamed(IDR_AURA_UBER_TRAY_CAST_DEVICE_ICON)
433 .ToImageSkia();
434
435 HoverHighlightView* container = new HoverHighlightView(this);
436 container->AddIconAndLabelCustomSize(
437 image, base::UTF8ToUTF16(sink_route->sink->name), false,
438 kTrayPopupDetailsIconWidth, kTrayPopupPaddingHorizontal,
439 kTrayPopupPaddingBetweenItems);
440
441 scroll_content()->AddChildView(container);
442 return container;
443 }
444
445 void CastDetailedView::HandleViewClicked(views::View* view) {
446 // Find the receiver we are going to cast to.
447 auto it = view_to_sink_map_.find(view);
448 if (it != view_to_sink_map_.end()) {
449 WmShell::Get()->cast_config()->CastToSink(it->second.Clone());
450 WmShell::Get()->RecordUserMetricsAction(
451 UMA_STATUS_AREA_DETAILED_CAST_VIEW_LAUNCH_CAST);
452 }
453 }
454
455 } // namespace tray
456
457 TrayCast::TrayCast(SystemTray* system_tray)
458 : SystemTrayItem(system_tray, UMA_CAST) {
459 WmShell::Get()->AddShellObserver(this);
460 WmShell::Get()->cast_config()->AddObserver(this);
461 WmShell::Get()->cast_config()->RequestDeviceRefresh();
462 }
463
464 TrayCast::~TrayCast() {
465 WmShell::Get()->cast_config()->RemoveObserver(this);
466 WmShell::Get()->RemoveShellObserver(this);
467 }
468
469 void TrayCast::StartCastForTest(const std::string& receiver_id) {
470 if (detailed_ != nullptr)
471 detailed_->SimulateViewClickedForTest(receiver_id);
472 }
473
474 void TrayCast::StopCastForTest() {
475 default_->cast_view()->StopCasting();
476 }
477
478 const std::string& TrayCast::GetDisplayedCastId() {
479 return default_->cast_view()->displayed_route_id();
480 }
481
482 const views::View* TrayCast::GetDefaultView() const {
483 return default_;
484 }
485
486 views::View* TrayCast::CreateTrayView(LoginStatus status) {
487 CHECK(tray_ == nullptr);
488 tray_ = new tray::CastTrayView(this);
489 tray_->SetVisible(HasActiveRoute());
490 return tray_;
491 }
492
493 views::View* TrayCast::CreateDefaultView(LoginStatus status) {
494 CHECK(default_ == nullptr);
495
496 default_ = new tray::CastDuplexView(this, status != LoginStatus::LOCKED,
497 sinks_and_routes_);
498 default_->set_id(TRAY_VIEW);
499 default_->select_view()->set_id(SELECT_VIEW);
500 default_->cast_view()->set_id(CAST_VIEW);
501
502 UpdatePrimaryView();
503 return default_;
504 }
505
506 views::View* TrayCast::CreateDetailedView(LoginStatus status) {
507 WmShell::Get()->RecordUserMetricsAction(UMA_STATUS_AREA_DETAILED_CAST_VIEW);
508 CHECK(detailed_ == nullptr);
509 detailed_ = new tray::CastDetailedView(this, sinks_and_routes_);
510 return detailed_;
511 }
512
513 void TrayCast::DestroyTrayView() {
514 tray_ = nullptr;
515 }
516
517 void TrayCast::DestroyDefaultView() {
518 default_ = nullptr;
519 }
520
521 void TrayCast::DestroyDetailedView() {
522 detailed_ = nullptr;
523 }
524
525 void TrayCast::OnDevicesUpdated(std::vector<mojom::SinkAndRoutePtr> devices) {
526 sinks_and_routes_ = std::move(devices);
527 UpdatePrimaryView();
528
529 if (default_) {
530 bool has_receivers = !sinks_and_routes_.empty();
531 default_->SetVisible(has_receivers);
532 default_->cast_view()->UpdateLabel(sinks_and_routes_);
533 }
534 if (detailed_)
535 detailed_->UpdateReceiverList(sinks_and_routes_);
536 }
537
538 void TrayCast::UpdatePrimaryView() {
539 if (WmShell::Get()->cast_config()->Connected() &&
540 !sinks_and_routes_.empty()) {
541 if (default_) {
542 if (HasActiveRoute())
543 default_->ActivateCastView();
544 else
545 default_->ActivateSelectView();
546 }
547
548 if (tray_)
549 tray_->SetVisible(is_mirror_casting_);
550 } else {
551 if (default_)
552 default_->SetVisible(false);
553 if (tray_)
554 tray_->SetVisible(false);
555 }
556 }
557
558 bool TrayCast::HasActiveRoute() {
559 for (const auto& sr : sinks_and_routes_) {
560 if (!sr->route->title.empty() && sr->route->is_local_source)
561 return true;
562 }
563
564 return false;
565 }
566
567 void TrayCast::OnCastingSessionStartedOrStopped(bool started) {
568 is_mirror_casting_ = started;
569 UpdatePrimaryView();
570 }
571
572 } // namespace ash
OLDNEW
« no previous file with comments | « ash/common/system/chromeos/cast/tray_cast.h ('k') | ash/common/system/chromeos/devicetype_utils.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698