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

Side by Side Diff: ash/system/tray/system_tray.cc

Issue 10824153: Change Ash web notification behavior (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix unit tests Created 8 years, 4 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ash/system/tray/system_tray.h" 5 #include "ash/system/tray/system_tray.h"
6 6
7 #include "ash/shell.h" 7 #include "ash/shell.h"
8 #include "ash/shell/panel_window.h" 8 #include "ash/shell/panel_window.h"
9 #include "ash/shell_window_ids.h" 9 #include "ash/shell_window_ids.h"
10 #include "ash/system/audio/tray_volume.h" 10 #include "ash/system/audio/tray_volume.h"
11 #include "ash/system/bluetooth/tray_bluetooth.h" 11 #include "ash/system/bluetooth/tray_bluetooth.h"
12 #include "ash/system/brightness/tray_brightness.h" 12 #include "ash/system/brightness/tray_brightness.h"
13 #include "ash/system/date/tray_date.h" 13 #include "ash/system/date/tray_date.h"
14 #include "ash/system/drive/tray_drive.h" 14 #include "ash/system/drive/tray_drive.h"
15 #include "ash/system/ime/tray_ime.h" 15 #include "ash/system/ime/tray_ime.h"
16 #include "ash/system/locale/tray_locale.h" 16 #include "ash/system/locale/tray_locale.h"
17 #include "ash/system/network/tray_network.h" 17 #include "ash/system/network/tray_network.h"
18 #include "ash/system/network/tray_sms.h" 18 #include "ash/system/network/tray_sms.h"
19 #include "ash/system/power/power_status_observer.h" 19 #include "ash/system/power/power_status_observer.h"
20 #include "ash/system/power/power_supply_status.h" 20 #include "ash/system/power/power_supply_status.h"
21 #include "ash/system/power/tray_power.h" 21 #include "ash/system/power/tray_power.h"
22 #include "ash/system/settings/tray_settings.h" 22 #include "ash/system/settings/tray_settings.h"
23 #include "ash/system/status_area_widget.h"
23 #include "ash/system/tray/system_tray_bubble.h" 24 #include "ash/system/tray/system_tray_bubble.h"
24 #include "ash/system/tray/system_tray_delegate.h" 25 #include "ash/system/tray/system_tray_delegate.h"
25 #include "ash/system/tray/system_tray_item.h" 26 #include "ash/system/tray/system_tray_item.h"
26 #include "ash/system/tray/tray_constants.h" 27 #include "ash/system/tray/tray_constants.h"
27 #include "ash/system/tray_accessibility.h" 28 #include "ash/system/tray_accessibility.h"
28 #include "ash/system/tray_caps_lock.h" 29 #include "ash/system/tray_caps_lock.h"
29 #include "ash/system/tray_update.h" 30 #include "ash/system/tray_update.h"
30 #include "ash/system/user/login_status.h" 31 #include "ash/system/user/login_status.h"
31 #include "ash/system/user/tray_user.h" 32 #include "ash/system/user/tray_user.h"
32 #include "ash/wm/shelf_layout_manager.h" 33 #include "ash/wm/shelf_layout_manager.h"
33 #include "base/logging.h" 34 #include "base/logging.h"
34 #include "base/timer.h" 35 #include "base/timer.h"
35 #include "base/utf_string_conversions.h" 36 #include "base/utf_string_conversions.h"
36 #include "grit/ash_strings.h" 37 #include "grit/ash_strings.h"
37 #include "ui/aura/root_window.h" 38 #include "ui/aura/root_window.h"
38 #include "ui/base/accessibility/accessible_view_state.h" 39 #include "ui/base/accessibility/accessible_view_state.h"
39 #include "ui/base/events.h" 40 #include "ui/base/events.h"
40 #include "ui/base/l10n/l10n_util.h" 41 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/compositor/layer.h" 42 #include "ui/compositor/layer.h"
42 #include "ui/gfx/canvas.h" 43 #include "ui/gfx/canvas.h"
43 #include "ui/gfx/screen.h" 44 #include "ui/gfx/screen.h"
44 #include "ui/gfx/skia_util.h" 45 #include "ui/gfx/skia_util.h"
45 #include "ui/views/border.h" 46 #include "ui/views/border.h"
46 #include "ui/views/controls/label.h" 47 #include "ui/views/controls/label.h"
47 #include "ui/views/layout/box_layout.h" 48 #include "ui/views/layout/box_layout.h"
48 #include "ui/views/layout/fill_layout.h" 49 #include "ui/views/layout/fill_layout.h"
49 #include "ui/views/view.h" 50 #include "ui/views/view.h"
50 51
51 namespace {
52
53 // Adjust the size of SystemTrayContainer with additional padding.
54 const int kTrayContainerVerticalPaddingBottomAlignment = 1;
55 const int kTrayContainerHorizontalPaddingBottomAlignment = 1;
56 const int kTrayContainerVerticalPaddingVerticalAlignment = 1;
57 const int kTrayContainerHorizontalPaddingVerticalAlignment = 1;
58
59 } // namespace
60
61 namespace ash { 52 namespace ash {
62 53
63 namespace internal { 54 namespace internal {
64 55
65 // Container for all the items in the tray. The container auto-resizes the
66 // widget when necessary.
67 class SystemTrayContainer : public views::View {
68 public:
69 SystemTrayContainer() {}
70 virtual ~SystemTrayContainer() {}
71
72 void UpdateLayout(ShelfAlignment alignment) {
73 // Adjust the size of status tray dark background by adding additional
74 // empty border.
75 if (alignment == SHELF_ALIGNMENT_BOTTOM) {
76 set_border(views::Border::CreateEmptyBorder(
77 kTrayContainerVerticalPaddingBottomAlignment,
78 kTrayContainerHorizontalPaddingBottomAlignment,
79 kTrayContainerVerticalPaddingBottomAlignment,
80 kTrayContainerHorizontalPaddingBottomAlignment));
81 views::View::SetLayoutManager(new views::BoxLayout(
82 views::BoxLayout::kHorizontal, 0, 0, 0));
83 } else {
84 set_border(views::Border::CreateEmptyBorder(
85 kTrayContainerVerticalPaddingVerticalAlignment,
86 kTrayContainerHorizontalPaddingVerticalAlignment,
87 kTrayContainerVerticalPaddingVerticalAlignment,
88 kTrayContainerHorizontalPaddingVerticalAlignment));
89 views::View::SetLayoutManager(new views::BoxLayout(
90 views::BoxLayout::kVertical, 0, 0, 0));
91 }
92 PreferredSizeChanged();
93 }
94
95 private:
96 // Overridden from views::View.
97 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE {
98 PreferredSizeChanged();
99 }
100
101 virtual void ChildVisibilityChanged(View* child) OVERRIDE {
102 PreferredSizeChanged();
103 }
104
105 virtual void ViewHierarchyChanged(bool is_add,
106 View* parent,
107 View* child) OVERRIDE {
108 if (parent == this)
109 PreferredSizeChanged();
110 }
111
112 DISALLOW_COPY_AND_ASSIGN(SystemTrayContainer);
113 };
114
115 // Observe the tray layer animation and update the anchor when it changes. 56 // Observe the tray layer animation and update the anchor when it changes.
116 // TODO(stevenjb): Observe or mirror the actual animation, not just the start 57 // TODO(stevenjb): Observe or mirror the actual animation, not just the start
117 // and end points. 58 // and end points.
118 class SystemTrayLayerAnimationObserver : public ui::LayerAnimationObserver { 59 class SystemTrayLayerAnimationObserver : public ui::LayerAnimationObserver {
119 public: 60 public:
120 explicit SystemTrayLayerAnimationObserver(SystemTray* host) : host_(host) {} 61 explicit SystemTrayLayerAnimationObserver(SystemTray* host) : host_(host) {}
121 62
122 virtual void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) { 63 virtual void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) {
123 host_->UpdateNotificationAnchor(); 64 host_->UpdateNotificationAnchor();
124 } 65 }
(...skipping 13 matching lines...) Expand all
138 }; 79 };
139 80
140 } // namespace internal 81 } // namespace internal
141 82
142 // SystemTray 83 // SystemTray
143 84
144 using internal::SystemTrayBubble; 85 using internal::SystemTrayBubble;
145 using internal::SystemTrayLayerAnimationObserver; 86 using internal::SystemTrayLayerAnimationObserver;
146 using internal::TrayBubbleView; 87 using internal::TrayBubbleView;
147 88
148 SystemTray::SystemTray() 89 SystemTray::SystemTray(internal::StatusAreaWidget* status_area_widget)
149 : items_(), 90 : internal::TrayBackgroundView(status_area_widget),
91 items_(),
150 accessibility_observer_(NULL), 92 accessibility_observer_(NULL),
151 audio_observer_(NULL), 93 audio_observer_(NULL),
152 bluetooth_observer_(NULL), 94 bluetooth_observer_(NULL),
153 brightness_observer_(NULL), 95 brightness_observer_(NULL),
154 caps_lock_observer_(NULL), 96 caps_lock_observer_(NULL),
155 clock_observer_(NULL), 97 clock_observer_(NULL),
156 drive_observer_(NULL), 98 drive_observer_(NULL),
157 ime_observer_(NULL), 99 ime_observer_(NULL),
158 locale_observer_(NULL), 100 locale_observer_(NULL),
159 network_observer_(NULL), 101 network_observer_(NULL),
160 update_observer_(NULL), 102 update_observer_(NULL),
161 user_observer_(NULL), 103 user_observer_(NULL),
162 should_show_launcher_(false), 104 should_show_launcher_(false),
163 default_bubble_height_(0), 105 default_bubble_height_(0),
164 hide_notifications_(false) { 106 hide_notifications_(false) {
165 tray_container_ = new internal::SystemTrayContainer;
166 tray_container_->UpdateLayout(shelf_alignment());
167 SetContents(tray_container_);
168 SetBorder();
169 } 107 }
170 108
171 SystemTray::~SystemTray() { 109 SystemTray::~SystemTray() {
172 bubble_.reset(); 110 bubble_.reset();
173 for (std::vector<SystemTrayItem*>::iterator it = items_.begin(); 111 for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
174 it != items_.end(); 112 it != items_.end();
175 ++it) { 113 ++it) {
176 (*it)->DestroyTrayView(); 114 (*it)->DestroyTrayView();
177 } 115 }
178 } 116 }
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 176
239 void SystemTray::AddTrayItem(SystemTrayItem* item) { 177 void SystemTray::AddTrayItem(SystemTrayItem* item) {
240 items_.push_back(item); 178 items_.push_back(item);
241 179
242 SystemTrayDelegate* delegate = Shell::GetInstance()->tray_delegate(); 180 SystemTrayDelegate* delegate = Shell::GetInstance()->tray_delegate();
243 views::View* tray_item = item->CreateTrayView(delegate->GetUserLoginStatus()); 181 views::View* tray_item = item->CreateTrayView(delegate->GetUserLoginStatus());
244 item->UpdateAfterShelfAlignmentChange( 182 item->UpdateAfterShelfAlignmentChange(
245 ash::Shell::GetInstance()->system_tray()->shelf_alignment()); 183 ash::Shell::GetInstance()->system_tray()->shelf_alignment());
246 184
247 if (tray_item) { 185 if (tray_item) {
248 tray_container_->AddChildViewAt(tray_item, 0); 186 tray_container()->AddChildViewAt(tray_item, 0);
249 PreferredSizeChanged(); 187 PreferredSizeChanged();
250 tray_item_map_[item] = tray_item; 188 tray_item_map_[item] = tray_item;
251 } 189 }
252 } 190 }
253 191
254 void SystemTray::RemoveTrayItem(SystemTrayItem* item) { 192 void SystemTray::RemoveTrayItem(SystemTrayItem* item) {
255 NOTIMPLEMENTED(); 193 NOTIMPLEMENTED();
256 } 194 }
257 195
258 void SystemTray::ShowDefaultView(BubbleCreationType creation_type) { 196 void SystemTray::ShowDefaultView(BubbleCreationType creation_type) {
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
322 (*it)->UpdateAfterShelfAlignmentChange(alignment); 260 (*it)->UpdateAfterShelfAlignmentChange(alignment);
323 } 261 }
324 } 262 }
325 263
326 void SystemTray::SetHideNotifications(bool hide_notifications) { 264 void SystemTray::SetHideNotifications(bool hide_notifications) {
327 if (notification_bubble_.get()) 265 if (notification_bubble_.get())
328 notification_bubble_->SetVisible(!hide_notifications); 266 notification_bubble_->SetVisible(!hide_notifications);
329 hide_notifications_ = hide_notifications; 267 hide_notifications_ = hide_notifications;
330 } 268 }
331 269
332 bool SystemTray::IsBubbleVisible() const { 270 bool SystemTray::IsAnyBubbleVisible() const {
333 return bubble_.get() && bubble_->IsVisible(); 271 if (bubble_.get() && bubble_->IsVisible())
272 return true;
273 if (notification_bubble_.get() && notification_bubble_->IsVisible())
274 return true;
275 return false;
334 } 276 }
335 277
336 bool SystemTray::CloseBubbleForTest() const { 278 bool SystemTray::CloseBubbleForTest() const {
337 if (!bubble_.get()) 279 if (!bubble_.get())
338 return false; 280 return false;
339 bubble_->Close(); 281 bubble_->Close();
340 return true; 282 return true;
341 } 283 }
342 284
343 // Private methods. 285 // Private methods.
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
406 // Destroy the notification bubble here so that it doesn't get rebuilt 348 // Destroy the notification bubble here so that it doesn't get rebuilt
407 // while we add items to the main bubble_ (e.g. in HideNotificationView). 349 // while we add items to the main bubble_ (e.g. in HideNotificationView).
408 notification_bubble_.reset(); 350 notification_bubble_.reset();
409 351
410 if (bubble_.get() && creation_type == BUBBLE_USE_EXISTING) { 352 if (bubble_.get() && creation_type == BUBBLE_USE_EXISTING) {
411 bubble_->UpdateView(items, bubble_type); 353 bubble_->UpdateView(items, bubble_type);
412 } else { 354 } else {
413 bubble_.reset(new SystemTrayBubble(this, items, bubble_type)); 355 bubble_.reset(new SystemTrayBubble(this, items, bubble_type));
414 ash::SystemTrayDelegate* delegate = 356 ash::SystemTrayDelegate* delegate =
415 ash::Shell::GetInstance()->tray_delegate(); 357 ash::Shell::GetInstance()->tray_delegate();
416 views::View* anchor = tray_container_; 358 views::View* anchor = tray_container();
417 TrayBubbleView::InitParams init_params(TrayBubbleView::ANCHOR_TYPE_TRAY, 359 TrayBubbleView::InitParams init_params(TrayBubbleView::ANCHOR_TYPE_TRAY,
418 shelf_alignment()); 360 shelf_alignment());
419 init_params.can_activate = can_activate; 361 init_params.can_activate = can_activate;
420 if (detailed) { 362 if (detailed) {
421 // This is the case where a volume control or brightness control bubble 363 // This is the case where a volume control or brightness control bubble
422 // is created. 364 // is created.
423 init_params.max_height = default_bubble_height_; 365 init_params.max_height = default_bubble_height_;
424 init_params.arrow_color = kBackgroundColor; 366 init_params.arrow_color = kBackgroundColor;
425 } 367 }
426 init_params.arrow_offset = arrow_offset; 368 init_params.arrow_offset = arrow_offset;
427 bubble_->InitView(anchor, init_params, delegate->GetUserLoginStatus()); 369 bubble_->InitView(anchor, init_params, delegate->GetUserLoginStatus());
428 } 370 }
429 // Save height of default view for creating detailed views directly. 371 // Save height of default view for creating detailed views directly.
430 if (!detailed) 372 if (!detailed)
431 default_bubble_height_ = bubble_->bubble_view()->height(); 373 default_bubble_height_ = bubble_->bubble_view()->height();
432 374
433 if (detailed && items.size() > 0) 375 if (detailed && items.size() > 0)
434 detailed_item_ = items[0]; 376 detailed_item_ = items[0];
435 else 377 else
436 detailed_item_ = NULL; 378 detailed_item_ = NULL;
437 379
438 // If we have focus the shelf should be visible and we need to continue 380 // If we have focus the shelf should be visible and we need to continue
439 // showing the shelf when the popup is shown. 381 // showing the shelf when the popup is shown.
440 if (GetWidget()->IsActive()) 382 if (GetWidget()->IsActive())
441 should_show_launcher_ = true; 383 should_show_launcher_ = true;
442 384
443 UpdateNotificationBubble(); // State changed, re-create notifications. 385 UpdateNotificationBubble(); // State changed, re-create notifications.
386 status_area_widget()->HideNonSystemNotifications();
444 } 387 }
445 388
446 void SystemTray::UpdateNotificationBubble() { 389 void SystemTray::UpdateNotificationBubble() {
447 // Only show the notification buble if we have notifications and we are not 390 // Only show the notification buble if we have notifications and we are not
448 // showing the default bubble. 391 // showing the default bubble.
449 if (notification_items_.empty() || 392 if (notification_items_.empty() ||
450 (bubble_.get() && 393 (bubble_.get() &&
451 bubble_->bubble_type() == SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) { 394 bubble_->bubble_type() == SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) {
452 notification_bubble_.reset(); 395 notification_bubble_.reset();
453 return; 396 return;
(...skipping 14 matching lines...) Expand all
468 // Show all notifications. 411 // Show all notifications.
469 notification_bubble_.reset(new SystemTrayBubble( 412 notification_bubble_.reset(new SystemTrayBubble(
470 this, notification_items_, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION)); 413 this, notification_items_, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION));
471 } 414 }
472 views::View* anchor; 415 views::View* anchor;
473 TrayBubbleView::AnchorType anchor_type; 416 TrayBubbleView::AnchorType anchor_type;
474 if (bubble_.get()) { 417 if (bubble_.get()) {
475 anchor = bubble_->bubble_view(); 418 anchor = bubble_->bubble_view();
476 anchor_type = TrayBubbleView::ANCHOR_TYPE_BUBBLE; 419 anchor_type = TrayBubbleView::ANCHOR_TYPE_BUBBLE;
477 } else { 420 } else {
478 anchor = tray_container_; 421 anchor = tray_container();
479 anchor_type = TrayBubbleView::ANCHOR_TYPE_TRAY; 422 anchor_type = TrayBubbleView::ANCHOR_TYPE_TRAY;
480 } 423 }
481 TrayBubbleView::InitParams init_params(anchor_type, shelf_alignment()); 424 TrayBubbleView::InitParams init_params(anchor_type, shelf_alignment());
482 init_params.arrow_offset = GetTrayXOffset(notification_items_[0]); 425 init_params.arrow_offset = GetTrayXOffset(notification_items_[0]);
483 init_params.arrow_color = kBackgroundColor; 426 init_params.arrow_color = kBackgroundColor;
484 user::LoginStatus login_status = 427 user::LoginStatus login_status =
485 Shell::GetInstance()->tray_delegate()->GetUserLoginStatus(); 428 Shell::GetInstance()->tray_delegate()->GetUserLoginStatus();
486 notification_bubble_->InitView(anchor, init_params, login_status); 429 notification_bubble_->InitView(anchor, init_params, login_status);
487 if (hide_notifications_) 430 if (hide_notifications_)
488 notification_bubble_->SetVisible(false); 431 notification_bubble_->SetVisible(false);
432 else
433 status_area_widget()->HideNonSystemNotifications();
489 } 434 }
490 435
491 void SystemTray::UpdateNotificationAnchor() { 436 void SystemTray::UpdateNotificationAnchor() {
492 if (!notification_bubble_.get()) 437 if (!notification_bubble_.get())
493 return; 438 return;
494 notification_bubble_->bubble_view()->UpdateBubble(); 439 notification_bubble_->bubble_view()->UpdateBubble();
495 // Ensure that the notification buble is above the launcher/status area. 440 // Ensure that the notification buble is above the launcher/status area.
496 notification_bubble_->bubble_view()->GetWidget()->StackAtTop(); 441 notification_bubble_->bubble_view()->GetWidget()->StackAtTop();
497 } 442 }
498 443
499 void SystemTray::SetBorder() {
500 // Change the border padding for different shelf alignment.
501 if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM) {
502 set_border(views::Border::CreateEmptyBorder(0, 0,
503 kPaddingFromBottomOfScreenBottomAlignment,
504 kPaddingFromRightEdgeOfScreenBottomAlignment));
505 } else if (shelf_alignment() == SHELF_ALIGNMENT_LEFT) {
506 set_border(views::Border::CreateEmptyBorder(0,
507 kPaddingFromOuterEdgeOfLauncherVerticalAlignment,
508 kPaddingFromBottomOfScreenVerticalAlignment,
509 kPaddingFromInnerEdgeOfLauncherVerticalAlignment));
510 } else {
511 set_border(views::Border::CreateEmptyBorder(0,
512 kPaddingFromInnerEdgeOfLauncherVerticalAlignment,
513 kPaddingFromBottomOfScreenVerticalAlignment,
514 kPaddingFromOuterEdgeOfLauncherVerticalAlignment));
515 }
516 }
517
518 void SystemTray::SetShelfAlignment(ShelfAlignment alignment) { 444 void SystemTray::SetShelfAlignment(ShelfAlignment alignment) {
519 if (alignment == shelf_alignment()) 445 if (alignment == shelf_alignment())
520 return; 446 return;
521 internal::TrayBackgroundView::SetShelfAlignment(alignment); 447 internal::TrayBackgroundView::SetShelfAlignment(alignment);
522 UpdateAfterShelfAlignmentChange(alignment); 448 UpdateAfterShelfAlignmentChange(alignment);
523 SetBorder();
524 tray_container_->UpdateLayout(alignment);
525 // Destroy any existing bubble so that it is rebuilt correctly. 449 // Destroy any existing bubble so that it is rebuilt correctly.
526 bubble_.reset(); 450 bubble_.reset();
527 // Rebuild any notification bubble. 451 // Rebuild any notification bubble.
528 if (notification_bubble_.get()) { 452 if (notification_bubble_.get()) {
529 notification_bubble_.reset(); 453 notification_bubble_.reset();
530 UpdateNotificationBubble(); 454 UpdateNotificationBubble();
531 } 455 }
532 } 456 }
533 457
534 bool SystemTray::PerformAction(const views::Event& event) { 458 bool SystemTray::PerformAction(const views::Event& event) {
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
579 503
580 void SystemTray::OnPaintFocusBorder(gfx::Canvas* canvas) { 504 void SystemTray::OnPaintFocusBorder(gfx::Canvas* canvas) {
581 // The tray itself expands to the right and bottom edge of the screen to make 505 // The tray itself expands to the right and bottom edge of the screen to make
582 // sure clicking on the edges brings up the popup. However, the focus border 506 // sure clicking on the edges brings up the popup. However, the focus border
583 // should be only around the container. 507 // should be only around the container.
584 if (GetWidget() && GetWidget()->IsActive()) 508 if (GetWidget() && GetWidget()->IsActive())
585 DrawBorder(canvas, GetContentsBounds()); 509 DrawBorder(canvas, GetContentsBounds());
586 } 510 }
587 511
588 } // namespace ash 512 } // namespace ash
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698