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

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

Issue 1118613003: Add UI for cast system tray integration (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix broken unit tests Created 5 years, 7 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/system/cast/tray_cast.h"
6
7 #include "ash/cast_config_delegate.h"
8 #include "ash/session/session_state_delegate.h"
9 #include "ash/shelf/shelf_types.h"
10 #include "ash/shell.h"
11 #include "ash/system/chromeos/screen_security/screen_tray_item.h"
12 #include "ash/system/tray/fixed_sized_image_view.h"
13 #include "ash/system/tray/fixed_sized_scroll_view.h"
14 #include "ash/system/tray/hover_highlight_view.h"
15 #include "ash/system/tray/system_tray.h"
16 #include "ash/system/tray/system_tray_delegate.h"
17 #include "ash/system/tray/system_tray_notifier.h"
18 #include "ash/system/tray/throbber_view.h"
19 #include "ash/system/tray/tray_constants.h"
20 #include "ash/system/tray/tray_details_view.h"
21 #include "ash/system/tray/tray_item_more.h"
22 #include "ash/system/tray/tray_item_view.h"
23 #include "ash/system/tray/tray_popup_label_button.h"
24 #include "base/bind.h"
25 #include "base/memory/weak_ptr.h"
26 #include "grit/ash_resources.h"
27 #include "grit/ash_strings.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "ui/gfx/image/image.h"
31 #include "ui/views/controls/button/button.h"
32 #include "ui/views/controls/image_view.h"
33 #include "ui/views/controls/label.h"
34 #include "ui/views/layout/box_layout.h"
35 #include "ui/views/layout/fill_layout.h"
36
37 namespace ash {
38
39 namespace {
40 const int kStopButtonRightPadding = 18;
41
42 // Callback helper for StopCast().
43 void StopCastCallback(
44 CastConfigDelegate* cast_config,
45 const CastConfigDelegate::ReceiversAndActivites& receivers_activities) {
46 for (auto& item : receivers_activities) {
47 CastConfigDelegate::Activity activity = item.second.activity;
48 if (activity.allow_stop && activity.id.empty() == false)
49 cast_config->StopCasting(activity.id);
50 }
51 }
52
53 // Stops currently casting device.
54 void StopCast() {
55 CastConfigDelegate* cast_config =
56 Shell::GetInstance()->system_tray_delegate()->GetCastConfigDelegate();
57 if (cast_config && cast_config->HasCastExtension()) {
58 cast_config->GetReceiversAndActivities(
59 base::Bind(&StopCastCallback, cast_config));
60 }
61 }
62
63 } // namespace
64
65 namespace tray {
66
67 // This view is displayed in the system tray when the cast extension is active.
68 // It asks the user if they want to cast the desktop. If they click on the
69 // chevron, then a detail view will replace this view where the user will
70 // actually pick the cast receiver.
71 class CastSelectDefaultView : public TrayItemMore {
72 public:
73 CastSelectDefaultView(SystemTrayItem* owner,
74 CastConfigDelegate* cast_config_delegate,
75 bool show_more);
76 ~CastSelectDefaultView() override;
77
78 // Updates the label based on the current set of receivers (if there are or
79 // are not any available receivers).
80 void UpdateLabel();
81
82 private:
83 void UpdateLabelCallback(
84 const CastConfigDelegate::ReceiversAndActivites& receivers_activities);
85
86 CastConfigDelegate* cast_config_delegate_;
87 DISALLOW_COPY_AND_ASSIGN(CastSelectDefaultView);
88 };
89
90 CastSelectDefaultView::CastSelectDefaultView(
91 SystemTrayItem* owner,
92 CastConfigDelegate* cast_config_delegate,
93 bool show_more)
94 : TrayItemMore(owner, show_more),
95 cast_config_delegate_(cast_config_delegate) {
96 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
97 SetImage(rb.GetImageNamed(IDR_AURA_UBER_TRAY_CAST).ToImageSkia());
98
99 // We first set a default label before we actually know what the label will
100 // be, because it could take awhile before UpdateLabel() actually applies
101 // the correct label.
102 SetLabel(rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_NO_DEVICE));
103 UpdateLabel();
104 }
105
106 CastSelectDefaultView::~CastSelectDefaultView() {
107 }
108
109 void CastSelectDefaultView::UpdateLabelCallback(
110 const CastConfigDelegate::ReceiversAndActivites& receivers_activities) {
111 // The label needs to reflect if there are no cast receivers
112 const base::string16 label =
113 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
114 receivers_activities.empty() ? IDS_ASH_STATUS_TRAY_CAST_NO_DEVICE
115 : IDS_ASH_STATUS_TRAY_CAST_DESKTOP);
116 SetLabel(label);
117 SetAccessibleName(label);
118 SetVisible(true);
119 }
120
121 void CastSelectDefaultView::UpdateLabel() {
122 if (cast_config_delegate_ == nullptr ||
123 cast_config_delegate_->HasCastExtension() == false)
124 return;
125
126 cast_config_delegate_->GetReceiversAndActivities(base::Bind(
127 &CastSelectDefaultView::UpdateLabelCallback, base::Unretained(this)));
128 }
129
130 // This view is displayed when the screen is actively being casted; it allows
131 // the user to easily stop casting. It fully replaces the
132 // |CastSelectDefaultView| view inside of the |CastDuplexView|.
133 class CastCastView : public views::View, public views::ButtonListener {
134 public:
135 explicit CastCastView(CastConfigDelegate* cast_config_delegate);
136 ~CastCastView() override;
137
138 // Updates the label for the stop view to include information about the
139 // current device that is being casted.
140 void UpdateLabel();
141
142 private:
143 void UpdateLabelCallback(
144 const CastConfigDelegate::ReceiversAndActivites& receivers_activities);
145
146 // Overridden from views::View.
147 void Layout() override;
148 // Overridden from views::ButtonListener
149 void ButtonPressed(views::Button* sender, const ui::Event& event) override;
150
151 CastConfigDelegate* cast_config_delegate_;
152 views::ImageView* icon_;
153 views::View* label_container_;
154 views::Label* title_;
155 views::Label* details_;
156 TrayPopupLabelButton* stop_button_;
157
158 DISALLOW_COPY_AND_ASSIGN(CastCastView);
159 };
160
161 CastCastView::CastCastView(CastConfigDelegate* cast_config_delegate)
162 : cast_config_delegate_(cast_config_delegate) {
163 // We will initialize the primary tray view which shows a stop button here.
164
165 set_background(views::Background::CreateSolidBackground(kBackgroundColor));
166 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
167 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
168 kTrayPopupPaddingHorizontal, 0,
169 kTrayPopupPaddingBetweenItems));
170 icon_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
171 icon_->SetImage(
172 bundle.GetImageNamed(IDR_AURA_UBER_TRAY_CAST_ENABLED).ToImageSkia());
173 AddChildView(icon_);
174
175 // The view has two labels, one above the other. The top label (|title_|)
176 // specifies that we are, say, "Casting desktop". The bottom label
177 // (|details_|) specifies where we are casting to, ie, "SomeRandom cast"
178 label_container_ = new views::View;
179 label_container_->SetLayoutManager(
180 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
181
182 title_ = new views::Label;
183 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
184 title_->SetFontList(bundle.GetFontList(ui::ResourceBundle::BoldFont));
185 label_container_->AddChildView(title_);
186
187 details_ = new views::Label;
188 details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
189 details_->SetMultiLine(false);
190 details_->SetEnabledColor(kHeaderTextColorNormal);
191 label_container_->AddChildView(details_);
192
193 AddChildView(label_container_);
194
195 // Add the stop bottom on the far-right. We customize how this stop button is
196 // displayed inside of |Layout()|.
197 base::string16 stop_button_text =
198 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
199 IDS_ASH_STATUS_TRAY_CAST_STOP);
200 stop_button_ = new TrayPopupLabelButton(this, stop_button_text);
201 AddChildView(stop_button_);
202
203 UpdateLabel();
204 }
205
206 CastCastView::~CastCastView() {
207 }
208
209 void CastCastView::Layout() {
210 views::View::Layout();
211
212 // Give the stop button the space it requests.
213 gfx::Size stop_size = stop_button_->GetPreferredSize();
214 gfx::Rect stop_bounds(stop_size);
215 stop_bounds.set_x(width() - stop_size.width() - kStopButtonRightPadding);
216 stop_bounds.set_y((height() - stop_size.height()) / 2);
217 stop_button_->SetBoundsRect(stop_bounds);
218
219 // Adjust the label's bounds in case it got cut off by |stop_button_|.
220 if (label_container_->bounds().Intersects(stop_button_->bounds())) {
221 gfx::Rect label_bounds = label_container_->bounds();
222 label_bounds.set_width(stop_button_->x() - kTrayPopupPaddingBetweenItems -
223 label_container_->x());
224 label_container_->SetBoundsRect(label_bounds);
225 }
226
227 // Center the label.
228 // TODO(jdufault): Why doesn't this happen automatically?
229 const int extra_height =
230 height() - label_container_->GetPreferredSize().height();
231 label_container_->SetY(extra_height / 2);
232 }
233
234 void CastCastView::UpdateLabel() {
235 if (cast_config_delegate_ == nullptr ||
236 cast_config_delegate_->HasCastExtension() == false)
237 return;
238
239 cast_config_delegate_->GetReceiversAndActivities(
240 base::Bind(&CastCastView::UpdateLabelCallback, base::Unretained(this)));
241 }
242
243 void CastCastView::UpdateLabelCallback(
244 const CastConfigDelegate::ReceiversAndActivites& receivers_activities) {
245 for (auto& i : receivers_activities) {
246 const CastConfigDelegate::Receiver receiver = i.second.receiver;
247 const CastConfigDelegate::Activity activity = i.second.activity;
248 if (!activity.id.empty()) {
249 // We want to display different labels inside of the title depending on
250 // what we are actually casting - either the desktop, a tab, or a fallback
251 // that catches everything else (ie, an extension tab).
252 if (activity.tab_id == CastConfigDelegate::Activity::TabId::DESKTOP) {
253 title_->SetText(
254 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_CAST_DESKTOP));
255 } else if (activity.tab_id >= 0) {
256 title_->SetText(l10n_util::GetStringFUTF16(
257 IDS_ASH_STATUS_TRAY_CAST_CAST_TAB, activity.title));
258 } else {
259 // We will fallback to whatever the extension provides us
260 title_->SetText(activity.title);
261 }
262
263 details_->SetText(receiver.name);
264 Layout();
265 break;
266 }
267 }
268 }
269
270 void CastCastView::ButtonPressed(views::Button* sender,
271 const ui::Event& event) {
272 DCHECK(sender == stop_button_);
273 StopCast();
274 }
275
276 // This view by itself does very little. It acts as a front-end for managing
277 // which of the two child views (|CastSelectDefaultView| and |CastCastView|)
278 // is active.
279 class CastDuplexView : public views::View {
280 public:
281 CastDuplexView(SystemTrayItem* owner,
282 CastConfigDelegate* config_delegate,
283 bool show_more);
284 ~CastDuplexView() override;
285
286 // Activate either the casting or select view.
287 void ActivateCastView();
288 void ActivateSelectView();
289
290 CastSelectDefaultView* select_view() { return select_view_; }
291 CastCastView* cast_view() { return cast_view_; }
292
293 private:
294 // Overridden from views::View.
295 void Layout() override;
296
297 CastSelectDefaultView* select_view_;
298 CastCastView* cast_view_;
299
300 DISALLOW_COPY_AND_ASSIGN(CastDuplexView);
301 };
302
303 CastDuplexView::CastDuplexView(SystemTrayItem* owner,
304 CastConfigDelegate* config_delegate,
305 bool show_more) {
306 select_view_ = new CastSelectDefaultView(owner, config_delegate, show_more);
307 cast_view_ = new CastCastView(config_delegate);
308 SetLayoutManager(new views::FillLayout());
309 AddChildView(select_view_);
310 AddChildView(cast_view_);
311
312 ActivateSelectView();
313 }
314
315 CastDuplexView::~CastDuplexView() {
316 }
317
318 void CastDuplexView::ActivateCastView() {
319 select_view_->SetVisible(false);
320 cast_view_->SetVisible(true);
321 InvalidateLayout();
322 }
323
324 void CastDuplexView::ActivateSelectView() {
325 select_view_->SetVisible(true);
326 cast_view_->SetVisible(false);
327 InvalidateLayout();
328 }
329
330 void CastDuplexView::Layout() {
331 views::View::Layout();
332
333 if (select_view_->IsDrawn())
334 select_view_->SetBoundsRect(GetContentsBounds());
335 if (cast_view_->IsDrawn())
336 cast_view_->SetBoundsRect(GetContentsBounds());
337 }
338
339 // Exposes an icon in the tray. |TrayCast| manages the visiblity of this.
340 class CastTrayView : public TrayItemView {
341 public:
342 CastTrayView(SystemTrayItem* tray_item);
343 ~CastTrayView() override;
344
345 // Called when the tray alignment changes so that the icon can recenter
346 // itself.
347 void UpdateAlignment(ShelfAlignment alignment);
348
349 // Returns a weak pointer to this instance.
350 base::WeakPtr<CastTrayView> AsWeakPtr();
351
352 private:
353 base::WeakPtrFactory<CastTrayView> weak_ptr_factory_;
354 DISALLOW_COPY_AND_ASSIGN(CastTrayView);
355 };
356
357 CastTrayView::CastTrayView(SystemTrayItem* tray_item)
358 : TrayItemView(tray_item), weak_ptr_factory_(this) {
359 CreateImageView();
360
361 image_view()->SetImage(ui::ResourceBundle::GetSharedInstance()
362 .GetImageNamed(IDR_AURA_UBER_TRAY_CAST_STATUS)
363 .ToImageSkia());
364 }
365
366 CastTrayView::~CastTrayView() {
367 }
368
369 void CastTrayView::UpdateAlignment(ShelfAlignment alignment) {
370 // Center the item dependent on the orientation of the shelf.
371 views::BoxLayout::Orientation layout = views::BoxLayout::kHorizontal;
372 switch (alignment) {
373 case ash::SHELF_ALIGNMENT_BOTTOM:
374 case ash::SHELF_ALIGNMENT_TOP:
375 layout = views::BoxLayout::kHorizontal;
376 break;
377 case ash::SHELF_ALIGNMENT_LEFT:
378 case ash::SHELF_ALIGNMENT_RIGHT:
379 layout = views::BoxLayout::kVertical;
380 break;
381 }
382 SetLayoutManager(new views::BoxLayout(layout, 0, 0, 0));
383 Layout();
384 }
385
386 base::WeakPtr<CastTrayView> CastTrayView::AsWeakPtr() {
387 return weak_ptr_factory_.GetWeakPtr();
388 }
389
390 // This view displays a list of cast receivers that can be clicked on and casted
391 // to. It is activated by clicking on the chevron inside of
392 // |CastSelectDefaultView|.
393 class CastDetailedView : public TrayDetailsView, public ViewClickListener {
394 public:
395 CastDetailedView(SystemTrayItem* owner,
396 CastConfigDelegate* cast_config_delegate,
397 user::LoginStatus login);
398 ~CastDetailedView() override;
399
400 private:
401 void CreateItems();
402
403 void UpdateReceiverList();
404 void UpdateReceiverListCallback(
405 const CastConfigDelegate::ReceiversAndActivites&
406 new_receivers_and_activities);
407 void UpdateReceiverListFromCachedData();
408 views::View* AddToReceiverList(
409 const CastConfigDelegate::ReceiverAndActivity& receiverActivity);
410
411 void AppendSettingsEntries();
412 void AppendHeaderEntry();
413
414 // Overridden from ViewClickListener.
415 void OnViewClicked(views::View* sender) override;
416
417 CastConfigDelegate* cast_config_delegate_;
418 user::LoginStatus login_;
419 views::View* options_;
420 CastConfigDelegate::ReceiversAndActivites receivers_and_activities_;
421 // A mapping from the view pointer to the associated activity id
422 std::map<views::View*, std::string> receiver_activity_map_;
423
424 DISALLOW_COPY_AND_ASSIGN(CastDetailedView);
425 };
426
427 CastDetailedView::CastDetailedView(SystemTrayItem* owner,
428 CastConfigDelegate* cast_config_delegate,
429 user::LoginStatus login)
430 : TrayDetailsView(owner),
431 cast_config_delegate_(cast_config_delegate),
432 login_(login),
433 options_(nullptr) {
434 CreateItems();
435 UpdateReceiverList();
436 }
437
438 CastDetailedView::~CastDetailedView() {
439 }
440
441 void CastDetailedView::CreateItems() {
442 CreateScrollableList();
443 AppendSettingsEntries();
444 AppendHeaderEntry();
445 }
446
447 void CastDetailedView::UpdateReceiverList() {
448 cast_config_delegate_->GetReceiversAndActivities(base::Bind(
449 &CastDetailedView::UpdateReceiverListCallback, base::Unretained(this)));
450 }
451
452 void CastDetailedView::UpdateReceiverListCallback(
453 const CastConfigDelegate::ReceiversAndActivites&
454 new_receivers_and_activities) {
455 // Add/update existing.
456 for (auto i = new_receivers_and_activities.begin();
457 i != new_receivers_and_activities.end(); ++i) {
458 receivers_and_activities_[i->first] = i->second;
459 }
460 // Remove non-existent.
461 for (auto i = receivers_and_activities_.begin();
462 i != receivers_and_activities_.end(); ++i) {
463 if (new_receivers_and_activities.count(i->first) == 0)
464 receivers_and_activities_.erase(i->first);
465 }
466
467 // Update UI.
468 UpdateReceiverListFromCachedData();
469 Layout();
470 }
471
472 void CastDetailedView::UpdateReceiverListFromCachedData() {
473 // Remove all of the existing views.
474 receiver_activity_map_.clear();
475 scroll_content()->RemoveAllChildViews(true);
476
477 // Add a view for each receiver.
478 for (auto& it : receivers_and_activities_) {
479 const CastConfigDelegate::ReceiverAndActivity& receiver_activity =
480 it.second;
481 views::View* container = AddToReceiverList(receiver_activity);
482 receiver_activity_map_[container] = it.first;
483 }
484
485 scroll_content()->SizeToPreferredSize();
486 static_cast<views::View*>(scroller())->Layout();
487 }
488
489 views::View* CastDetailedView::AddToReceiverList(
490 const CastConfigDelegate::ReceiverAndActivity& receiverActivity) {
491 HoverHighlightView* container = new HoverHighlightView(this);
492
493 const gfx::ImageSkia* image =
494 ui::ResourceBundle::GetSharedInstance()
495 .GetImageNamed(IDR_AURA_UBER_TRAY_CAST_DEVICE_ICON)
496 .ToImageSkia();
497 const base::string16& name = receiverActivity.receiver.name;
498 container->AddIndentedIconAndLabel(*image, name, false);
499
500 scroll_content()->AddChildView(container);
501 return container;
502 }
503
504 void CastDetailedView::AppendSettingsEntries() {
505 // Settings requires a browser window, hide it for non logged in user.
506 const bool userAddingRunning = Shell::GetInstance()
507 ->session_state_delegate()
508 ->IsInSecondaryLoginScreen();
509
510 if (login_ == user::LOGGED_IN_NONE || login_ == user::LOGGED_IN_LOCKED ||
511 userAddingRunning)
512 return;
513
514 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
515 HoverHighlightView* container = new HoverHighlightView(this);
516 container->AddLabel(rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAST_OPTIONS),
517 gfx::ALIGN_LEFT, false /* highlight */);
518
519 AddChildView(container);
520 options_ = container;
521 }
522
523 void CastDetailedView::AppendHeaderEntry() {
524 CreateSpecialRow(IDS_ASH_STATUS_TRAY_CAST, this);
525 }
526
527 void CastDetailedView::OnViewClicked(views::View* sender) {
528 if (sender == footer()->content()) {
529 TransitionToDefaultView();
530 } else if (sender == options_) {
531 cast_config_delegate_->LaunchCastOptions();
532 } else {
533 // Find the receiver we are going to cast to
534 auto it = receiver_activity_map_.find(sender);
535 if (it != receiver_activity_map_.end()) {
536 cast_config_delegate_->CastToReceiver(it->second);
537 }
538 }
539 }
540
541 } // namespace tray
542
543 TrayCast::TrayCast(SystemTray* system_tray)
544 : SystemTrayItem(system_tray),
545 cast_config_delegate_(ash::Shell::GetInstance()
546 ->system_tray_delegate()
547 ->GetCastConfigDelegate()) {
548 Shell::GetInstance()->AddShellObserver(this);
549 }
550
551 TrayCast::~TrayCast() {
552 Shell::GetInstance()->RemoveShellObserver(this);
553 }
554
555 views::View* TrayCast::CreateTrayView(user::LoginStatus status) {
556 CHECK(tray_ == nullptr);
557 tray_ = new tray::CastTrayView(this);
558 tray_->SetVisible(is_casting_);
559 return tray_;
560 }
561
562 views::View* TrayCast::CreateDefaultView(user::LoginStatus status) {
563 CHECK(default_ == nullptr);
564 default_ = new tray::CastDuplexView(this, cast_config_delegate_,
565 status != user::LOGGED_IN_LOCKED);
566 UpdatePrimaryView();
567 return default_;
568 }
569
570 views::View* TrayCast::CreateDetailedView(user::LoginStatus status) {
571 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
572 ash::UMA_STATUS_AREA_DETAILED_CAST_VIEW);
573 CHECK(detailed_ == nullptr);
574 detailed_ = new tray::CastDetailedView(this, cast_config_delegate_, status);
575 return detailed_;
576 }
577
578 void TrayCast::DestroyTrayView() {
579 tray_ = nullptr;
580 }
581
582 void TrayCast::DestroyDefaultView() {
583 default_ = nullptr;
584 }
585
586 void TrayCast::DestroyDetailedView() {
587 detailed_ = nullptr;
588 }
589
590 bool TrayCast::HasCastExtension() {
591 return cast_config_delegate_ != nullptr &&
592 cast_config_delegate_->HasCastExtension();
593 }
594
595 void TrayCast::UpdatePrimaryView() {
596 if (HasCastExtension() == false) {
597 if (default_)
598 default_->SetVisible(false);
599 if (tray_) {
600 base::MessageLoopForUI::current()->PostTask(
601 FROM_HERE, base::Bind(&tray::CastTrayView::SetVisible,
602 tray_->AsWeakPtr(), false));
603 }
604 } else {
605 if (default_) {
606 if (is_casting_)
607 default_->ActivateCastView();
608 else
609 default_->ActivateSelectView();
610 }
611
612 if (tray_)
613 tray_->SetVisible(is_casting_);
614 }
615 }
616
617 void TrayCast::OnCastingSessionStartedOrStopped(bool started) {
618 is_casting_ = started;
619 UpdatePrimaryView();
620 }
621
622 void TrayCast::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
623 if (tray_)
624 tray_->UpdateAlignment(alignment);
625 }
626
627 } // namespace ash
OLDNEW
« no previous file with comments | « ash/system/cast/tray_cast.h ('k') | ash/system/chromeos/screen_security/screen_capture_tray_item.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698