app_mode: No system tray bubbles in kiosk app mode.
[chromium-blink-merge.git] / ash / system / tray / system_tray.cc
blobe3fcf788000688e5837f93680e52b610954fd0e1
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
3 // found in the LICENSE file.
5 #include "ash/system/tray/system_tray.h"
7 #include "ash/ash_switches.h"
8 #include "ash/shelf/shelf_layout_manager.h"
9 #include "ash/shell.h"
10 #include "ash/shell/panel_window.h"
11 #include "ash/shell_window_ids.h"
12 #include "ash/system/bluetooth/tray_bluetooth.h"
13 #include "ash/system/brightness/tray_brightness.h"
14 #include "ash/system/date/tray_date.h"
15 #include "ash/system/drive/tray_drive.h"
16 #include "ash/system/ime/tray_ime.h"
17 #include "ash/system/locale/tray_locale.h"
18 #include "ash/system/logout_button/tray_logout_button.h"
19 #include "ash/system/monitor/tray_monitor.h"
20 #include "ash/system/session_length_limit/tray_session_length_limit.h"
21 #include "ash/system/status_area_widget.h"
22 #include "ash/system/tray/system_tray_delegate.h"
23 #include "ash/system/tray/system_tray_item.h"
24 #include "ash/system/tray/tray_bubble_wrapper.h"
25 #include "ash/system/tray/tray_constants.h"
26 #include "ash/system/tray_accessibility.h"
27 #include "ash/system/tray_caps_lock.h"
28 #include "ash/system/tray_update.h"
29 #include "ash/system/user/login_status.h"
30 #include "ash/system/user/tray_user.h"
31 #include "base/command_line.h"
32 #include "base/logging.h"
33 #include "base/strings/utf_string_conversions.h"
34 #include "base/timer/timer.h"
35 #include "grit/ash_strings.h"
36 #include "ui/aura/root_window.h"
37 #include "ui/base/events/event_constants.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/compositor/layer.h"
40 #include "ui/gfx/canvas.h"
41 #include "ui/gfx/screen.h"
42 #include "ui/gfx/skia_util.h"
43 #include "ui/views/border.h"
44 #include "ui/views/controls/label.h"
45 #include "ui/views/layout/box_layout.h"
46 #include "ui/views/layout/fill_layout.h"
47 #include "ui/views/view.h"
49 #if defined(OS_CHROMEOS)
50 #include "ash/system/chromeos/audio/tray_audio.h"
51 #include "ash/system/chromeos/enterprise/tray_enterprise.h"
52 #include "ash/system/chromeos/managed/tray_locally_managed_user.h"
53 #include "ash/system/chromeos/network/tray_network.h"
54 #include "ash/system/chromeos/network/tray_sms.h"
55 #include "ash/system/chromeos/network/tray_vpn.h"
56 #include "ash/system/chromeos/power/tray_power.h"
57 #include "ash/system/chromeos/screen_security/screen_capture_tray_item.h"
58 #include "ash/system/chromeos/screen_security/screen_share_tray_item.h"
59 #include "ash/system/chromeos/settings/tray_settings.h"
60 #include "ash/system/chromeos/tray_display.h"
61 #include "ui/message_center/message_center.h"
62 #endif
64 using views::TrayBubbleView;
66 namespace ash {
68 // The minimum width of the system tray menu width.
69 const int kMinimumSystemTrayMenuWidth = 300;
71 namespace internal {
73 // Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper
74 // instances for a bubble.
76 class SystemBubbleWrapper {
77 public:
78 // Takes ownership of |bubble|.
79 explicit SystemBubbleWrapper(internal::SystemTrayBubble* bubble)
80 : bubble_(bubble) {
83 // Initializes the bubble view and creates |bubble_wrapper_|.
84 void InitView(TrayBackgroundView* tray,
85 views::View* anchor,
86 TrayBubbleView::InitParams* init_params) {
87 DCHECK(anchor);
88 user::LoginStatus login_status =
89 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
90 bubble_->InitView(anchor, login_status, init_params);
91 bubble_wrapper_.reset(
92 new internal::TrayBubbleWrapper(tray, bubble_->bubble_view()));
93 if (ash::switches::UseAlternateShelfLayout()) {
94 // The system bubble should not have an arrow.
95 bubble_->bubble_view()->SetArrowPaintType(
96 views::BubbleBorder::PAINT_NONE);
100 // Convenience accessors:
101 SystemTrayBubble* bubble() const { return bubble_.get(); }
102 SystemTrayBubble::BubbleType bubble_type() const {
103 return bubble_->bubble_type();
105 TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }
107 private:
108 scoped_ptr<internal::SystemTrayBubble> bubble_;
109 scoped_ptr<internal::TrayBubbleWrapper> bubble_wrapper_;
111 DISALLOW_COPY_AND_ASSIGN(SystemBubbleWrapper);
114 } // namespace internal
116 // SystemTray
118 using internal::SystemTrayBubble;
120 SystemTray::SystemTray(internal::StatusAreaWidget* status_area_widget)
121 : internal::TrayBackgroundView(status_area_widget),
122 items_(),
123 default_bubble_height_(0),
124 hide_notifications_(false),
125 tray_accessibility_(NULL) {
126 SetContentsBackground();
129 SystemTray::~SystemTray() {
130 // Destroy any child views that might have back pointers before ~View().
131 system_bubble_.reset();
132 notification_bubble_.reset();
133 for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
134 it != items_.end();
135 ++it) {
136 (*it)->DestroyTrayView();
140 void SystemTray::InitializeTrayItems(SystemTrayDelegate* delegate) {
141 internal::TrayBackgroundView::Initialize();
142 CreateItems(delegate);
145 void SystemTray::CreateItems(SystemTrayDelegate* delegate) {
146 #if !defined(OS_WIN)
147 AddTrayItem(new internal::TraySessionLengthLimit(this));
148 AddTrayItem(new internal::TrayLogoutButton(this));
149 // In multi-profile user mode we can have multiple user tiles.
150 ash::Shell* shell = ash::Shell::GetInstance();
151 int maximum_user_profiles =
152 shell->delegate()->IsMultiProfilesEnabled() ?
153 shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() :
155 // Note: We purposely use one more item then logged in users to account for
156 // the additional separator.
157 for (int i = 0; i <= maximum_user_profiles; i++)
158 AddTrayItem(new internal::TrayUser(this, i));
160 #endif
161 #if defined(OS_CHROMEOS)
162 AddTrayItem(new internal::TrayEnterprise(this));
163 AddTrayItem(new internal::TrayLocallyManagedUser(this));
164 #endif
165 AddTrayItem(new internal::TrayIME(this));
166 tray_accessibility_ = new internal::TrayAccessibility(this);
167 AddTrayItem(tray_accessibility_);
168 #if defined(OS_CHROMEOS)
169 AddTrayItem(
170 new internal::TrayPower(this, message_center::MessageCenter::Get()));
171 #endif
172 #if defined(OS_CHROMEOS)
173 AddTrayItem(new internal::TrayNetwork(this));
174 AddTrayItem(new internal::TrayVPN(this));
175 AddTrayItem(new internal::TraySms(this));
176 #endif
177 #if !defined(OS_WIN)
178 AddTrayItem(new internal::TrayBluetooth(this));
179 #endif
180 AddTrayItem(new internal::TrayDrive(this));
181 AddTrayItem(new internal::TrayLocale(this));
182 #if defined(OS_CHROMEOS)
183 AddTrayItem(new internal::TrayDisplay(this));
184 AddTrayItem(new internal::ScreenCaptureTrayItem(this));
185 AddTrayItem(new internal::ScreenShareTrayItem(this));
186 AddTrayItem(new internal::TrayAudio(this));
187 #endif
188 #if !defined(OS_WIN)
189 AddTrayItem(new internal::TrayBrightness(this));
190 AddTrayItem(new internal::TrayCapsLock(this));
191 #endif
192 #if defined(OS_CHROMEOS)
193 AddTrayItem(new internal::TraySettings(this));
194 #endif
195 AddTrayItem(new internal::TrayUpdate(this));
196 AddTrayItem(new internal::TrayDate(this));
198 #if defined(OS_LINUX)
199 // Add memory monitor if enabled.
200 CommandLine* cmd = CommandLine::ForCurrentProcess();
201 if (cmd->HasSwitch(ash::switches::kAshEnableMemoryMonitor))
202 AddTrayItem(new internal::TrayMonitor(this));
203 #endif
205 SetVisible(ash::Shell::GetInstance()->system_tray_delegate()->
206 GetTrayVisibilityOnStartup());
209 void SystemTray::AddTrayItem(SystemTrayItem* item) {
210 items_.push_back(item);
212 SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
213 views::View* tray_item = item->CreateTrayView(delegate->GetUserLoginStatus());
214 item->UpdateAfterShelfAlignmentChange(shelf_alignment());
216 if (tray_item) {
217 tray_container()->AddChildViewAt(tray_item, 0);
218 PreferredSizeChanged();
219 tray_item_map_[item] = tray_item;
223 void SystemTray::RemoveTrayItem(SystemTrayItem* item) {
224 NOTIMPLEMENTED();
227 const std::vector<SystemTrayItem*>& SystemTray::GetTrayItems() const {
228 return items_.get();
231 void SystemTray::ShowDefaultView(BubbleCreationType creation_type) {
232 ShowDefaultViewWithOffset(creation_type,
233 TrayBubbleView::InitParams::kArrowDefaultOffset);
236 void SystemTray::ShowDetailedView(SystemTrayItem* item,
237 int close_delay,
238 bool activate,
239 BubbleCreationType creation_type) {
240 std::vector<SystemTrayItem*> items;
241 items.push_back(item);
242 ShowItems(items, true, activate, creation_type, GetTrayXOffset(item));
243 if (system_bubble_)
244 system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
247 void SystemTray::SetDetailedViewCloseDelay(int close_delay) {
248 if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DETAILED))
249 system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
252 void SystemTray::HideDetailedView(SystemTrayItem* item) {
253 if (item != detailed_item_)
254 return;
255 DestroySystemBubble();
256 UpdateNotificationBubble();
259 void SystemTray::ShowNotificationView(SystemTrayItem* item) {
260 if (std::find(notification_items_.begin(), notification_items_.end(), item)
261 != notification_items_.end())
262 return;
263 notification_items_.push_back(item);
264 UpdateNotificationBubble();
267 void SystemTray::HideNotificationView(SystemTrayItem* item) {
268 std::vector<SystemTrayItem*>::iterator found_iter =
269 std::find(notification_items_.begin(), notification_items_.end(), item);
270 if (found_iter == notification_items_.end())
271 return;
272 notification_items_.erase(found_iter);
273 // Only update the notification bubble if visible (i.e. don't create one).
274 if (notification_bubble_)
275 UpdateNotificationBubble();
278 void SystemTray::UpdateAfterLoginStatusChange(user::LoginStatus login_status) {
279 DestroySystemBubble();
280 UpdateNotificationBubble();
282 for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
283 it != items_.end();
284 ++it) {
285 (*it)->UpdateAfterLoginStatusChange(login_status);
288 SetVisible(true);
289 PreferredSizeChanged();
292 void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
293 for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
294 it != items_.end();
295 ++it) {
296 (*it)->UpdateAfterShelfAlignmentChange(alignment);
300 void SystemTray::SetHideNotifications(bool hide_notifications) {
301 if (notification_bubble_)
302 notification_bubble_->bubble()->SetVisible(!hide_notifications);
303 hide_notifications_ = hide_notifications;
306 bool SystemTray::ShouldShowLauncher() const {
307 return system_bubble_.get() && system_bubble_->bubble()->ShouldShowLauncher();
310 bool SystemTray::HasSystemBubble() const {
311 return system_bubble_.get() != NULL;
314 bool SystemTray::HasNotificationBubble() const {
315 return notification_bubble_.get() != NULL;
318 bool SystemTray::IsPressed() {
319 return HasSystemBubble();
322 internal::SystemTrayBubble* SystemTray::GetSystemBubble() {
323 if (!system_bubble_)
324 return NULL;
325 return system_bubble_->bubble();
328 bool SystemTray::IsAnyBubbleVisible() const {
329 return ((system_bubble_.get() &&
330 system_bubble_->bubble()->IsVisible()) ||
331 (notification_bubble_.get() &&
332 notification_bubble_->bubble()->IsVisible()));
335 bool SystemTray::IsMouseInNotificationBubble() const {
336 if (!notification_bubble_)
337 return false;
338 return notification_bubble_->bubble_view()->GetBoundsInScreen().Contains(
339 Shell::GetScreen()->GetCursorScreenPoint());
342 bool SystemTray::CloseSystemBubble() const {
343 if (!system_bubble_)
344 return false;
345 system_bubble_->bubble()->Close();
346 return true;
349 bool SystemTray::CloseNotificationBubbleForTest() const {
350 if (!notification_bubble_)
351 return false;
352 notification_bubble_->bubble()->Close();
353 return true;
356 // Private methods.
358 bool SystemTray::HasSystemBubbleType(SystemTrayBubble::BubbleType type) {
359 DCHECK(type != SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
360 return system_bubble_.get() && system_bubble_->bubble_type() == type;
363 void SystemTray::DestroySystemBubble() {
364 system_bubble_.reset();
365 detailed_item_ = NULL;
368 void SystemTray::DestroyNotificationBubble() {
369 notification_bubble_.reset();
370 status_area_widget()->SetHideWebNotifications(false);
373 int SystemTray::GetTrayXOffset(SystemTrayItem* item) const {
374 // Don't attempt to align the arrow if the shelf is on the left or right.
375 if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM &&
376 shelf_alignment() != SHELF_ALIGNMENT_TOP)
377 return TrayBubbleView::InitParams::kArrowDefaultOffset;
379 std::map<SystemTrayItem*, views::View*>::const_iterator it =
380 tray_item_map_.find(item);
381 if (it == tray_item_map_.end())
382 return TrayBubbleView::InitParams::kArrowDefaultOffset;
384 const views::View* item_view = it->second;
385 if (item_view->bounds().IsEmpty()) {
386 // The bounds of item could be still empty if it does not have a visible
387 // tray view. In that case, use the default (minimum) offset.
388 return TrayBubbleView::InitParams::kArrowDefaultOffset;
391 gfx::Point point(item_view->width() / 2, 0);
392 ConvertPointToWidget(item_view, &point);
393 return point.x();
396 void SystemTray::ShowDefaultViewWithOffset(BubbleCreationType creation_type,
397 int arrow_offset) {
398 ShowItems(items_.get(), false, true, creation_type, arrow_offset);
401 void SystemTray::ShowItems(const std::vector<SystemTrayItem*>& items,
402 bool detailed,
403 bool can_activate,
404 BubbleCreationType creation_type,
405 int arrow_offset) {
406 // No system tray bubbles in kiosk mode.
407 if (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus() ==
408 ash::user::LOGGED_IN_KIOSK_APP) {
409 return;
412 // Destroy any existing bubble and create a new one.
413 SystemTrayBubble::BubbleType bubble_type = detailed ?
414 SystemTrayBubble::BUBBLE_TYPE_DETAILED :
415 SystemTrayBubble::BUBBLE_TYPE_DEFAULT;
417 // Destroy the notification bubble here so that it doesn't get rebuilt
418 // while we add items to the main bubble_ (e.g. in HideNotificationView).
419 notification_bubble_.reset();
421 if (system_bubble_.get() && creation_type == BUBBLE_USE_EXISTING) {
422 system_bubble_->bubble()->UpdateView(items, bubble_type);
423 } else {
424 // The menu width is fixed, and it is a per language setting.
425 int menu_width = std::max(kMinimumSystemTrayMenuWidth,
426 Shell::GetInstance()->system_tray_delegate()->GetSystemTrayMenuWidth());
428 TrayBubbleView::InitParams init_params(TrayBubbleView::ANCHOR_TYPE_TRAY,
429 GetAnchorAlignment(),
430 menu_width,
431 kTrayPopupMaxWidth);
432 init_params.can_activate = can_activate;
433 if (detailed) {
434 // This is the case where a volume control or brightness control bubble
435 // is created.
436 init_params.max_height = default_bubble_height_;
437 init_params.arrow_color = kBackgroundColor;
438 } else {
439 init_params.arrow_color = kHeaderBackgroundColor;
441 init_params.arrow_offset = arrow_offset;
442 // For Volume and Brightness we don't want to show an arrow when
443 // they are shown in a bubble by themselves.
444 init_params.arrow_paint_type = views::BubbleBorder::PAINT_NORMAL;
445 if (items.size() == 1 && items[0]->ShouldHideArrow())
446 init_params.arrow_paint_type = views::BubbleBorder::PAINT_TRANSPARENT;
447 SystemTrayBubble* bubble = new SystemTrayBubble(this, items, bubble_type);
448 system_bubble_.reset(new internal::SystemBubbleWrapper(bubble));
449 system_bubble_->InitView(this, tray_container(), &init_params);
451 // Save height of default view for creating detailed views directly.
452 if (!detailed)
453 default_bubble_height_ = system_bubble_->bubble_view()->height();
455 if (detailed && items.size() > 0)
456 detailed_item_ = items[0];
457 else
458 detailed_item_ = NULL;
460 UpdateNotificationBubble(); // State changed, re-create notifications.
461 status_area_widget()->SetHideWebNotifications(true);
462 GetShelfLayoutManager()->UpdateAutoHideState();
465 void SystemTray::UpdateNotificationBubble() {
466 // Only show the notification buble if we have notifications.
467 if (notification_items_.empty()) {
468 DestroyNotificationBubble();
469 return;
471 // Destroy the existing bubble before constructing a new one.
472 notification_bubble_.reset();
473 SystemTrayBubble* notification_bubble;
474 notification_bubble = new SystemTrayBubble(
475 this, notification_items_, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
476 views::View* anchor;
477 TrayBubbleView::AnchorType anchor_type;
478 // Tray items might want to show notifications while we are creating and
479 // initializing the |system_bubble_| - but it might not be fully initialized
480 // when coming here - this would produce a crashed like crbug.com/247416.
481 // As such we check the existence of the widget here.
482 if (system_bubble_.get() &&
483 system_bubble_->bubble_view() &&
484 system_bubble_->bubble_view()->GetWidget()) {
485 anchor = system_bubble_->bubble_view();
486 anchor_type = TrayBubbleView::ANCHOR_TYPE_BUBBLE;
487 } else {
488 anchor = tray_container();
489 anchor_type = TrayBubbleView::ANCHOR_TYPE_TRAY;
491 TrayBubbleView::InitParams init_params(anchor_type,
492 GetAnchorAlignment(),
493 kTrayPopupMinWidth,
494 kTrayPopupMaxWidth);
495 init_params.arrow_color = kBackgroundColor;
496 init_params.arrow_offset = GetTrayXOffset(notification_items_[0]);
497 notification_bubble_.reset(
498 new internal::SystemBubbleWrapper(notification_bubble));
499 notification_bubble_->InitView(this, anchor, &init_params);
501 if (notification_bubble->bubble_view()->child_count() == 0) {
502 // It is possible that none of the items generated actual notifications.
503 DestroyNotificationBubble();
504 return;
506 if (hide_notifications_)
507 notification_bubble->SetVisible(false);
508 else
509 status_area_widget()->SetHideWebNotifications(true);
512 void SystemTray::SetShelfAlignment(ShelfAlignment alignment) {
513 if (alignment == shelf_alignment())
514 return;
515 internal::TrayBackgroundView::SetShelfAlignment(alignment);
516 UpdateAfterShelfAlignmentChange(alignment);
517 // Destroy any existing bubble so that it is rebuilt correctly.
518 system_bubble_.reset();
519 // Rebuild any notification bubble.
520 if (notification_bubble_) {
521 notification_bubble_.reset();
522 UpdateNotificationBubble();
526 void SystemTray::AnchorUpdated() {
527 if (notification_bubble_) {
528 notification_bubble_->bubble_view()->UpdateBubble();
529 // Ensure that the notification buble is above the launcher/status area.
530 notification_bubble_->bubble_view()->GetWidget()->StackAtTop();
531 UpdateBubbleViewArrow(notification_bubble_->bubble_view());
533 if (system_bubble_) {
534 system_bubble_->bubble_view()->UpdateBubble();
535 if (!ash::switches::UseAlternateShelfLayout())
536 UpdateBubbleViewArrow(system_bubble_->bubble_view());
540 base::string16 SystemTray::GetAccessibleNameForTray() {
541 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME);
544 void SystemTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {
545 if (system_bubble_.get() && bubble_view == system_bubble_->bubble_view()) {
546 DestroySystemBubble();
547 UpdateNotificationBubble(); // State changed, re-create notifications.
548 GetShelfLayoutManager()->UpdateAutoHideState();
549 } else if (notification_bubble_.get() &&
550 bubble_view == notification_bubble_->bubble_view()) {
551 DestroyNotificationBubble();
555 bool SystemTray::ClickedOutsideBubble() {
556 if (!system_bubble_)
557 return false;
558 HideBubbleWithView(system_bubble_->bubble_view());
559 return true;
562 void SystemTray::BubbleViewDestroyed() {
563 if (system_bubble_) {
564 system_bubble_->bubble()->DestroyItemViews();
565 system_bubble_->bubble()->BubbleViewDestroyed();
569 void SystemTray::OnMouseEnteredView() {
570 if (system_bubble_)
571 system_bubble_->bubble()->StopAutoCloseTimer();
574 void SystemTray::OnMouseExitedView() {
575 if (system_bubble_)
576 system_bubble_->bubble()->RestartAutoCloseTimer();
579 base::string16 SystemTray::GetAccessibleNameForBubble() {
580 return GetAccessibleNameForTray();
583 gfx::Rect SystemTray::GetAnchorRect(
584 views::Widget* anchor_widget,
585 TrayBubbleView::AnchorType anchor_type,
586 TrayBubbleView::AnchorAlignment anchor_alignment) {
587 return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
590 void SystemTray::HideBubble(const TrayBubbleView* bubble_view) {
591 HideBubbleWithView(bubble_view);
594 bool SystemTray::PerformAction(const ui::Event& event) {
595 // If we're already showing the default view, hide it; otherwise, show it
596 // (and hide any popup that's currently shown).
597 if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) {
598 system_bubble_->bubble()->Close();
599 } else {
600 int arrow_offset = TrayBubbleView::InitParams::kArrowDefaultOffset;
601 if (event.IsMouseEvent() || event.type() == ui::ET_GESTURE_TAP) {
602 const ui::LocatedEvent& located_event =
603 static_cast<const ui::LocatedEvent&>(event);
604 if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM ||
605 shelf_alignment() == SHELF_ALIGNMENT_TOP) {
606 gfx::Point point(located_event.x(), 0);
607 ConvertPointToWidget(this, &point);
608 arrow_offset = point.x();
611 ShowDefaultViewWithOffset(BUBBLE_CREATE_NEW, arrow_offset);
613 return true;
616 } // namespace ash