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

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