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"
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"
64 using views::TrayBubbleView
;
68 // The minimum width of the system tray menu width.
69 const int kMinimumSystemTrayMenuWidth
= 300;
73 // Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper
74 // instances for a bubble.
76 class SystemBubbleWrapper
{
78 // Takes ownership of |bubble|.
79 explicit SystemBubbleWrapper(internal::SystemTrayBubble
* bubble
)
83 // Initializes the bubble view and creates |bubble_wrapper_|.
84 void InitView(TrayBackgroundView
* tray
,
86 TrayBubbleView::InitParams
* init_params
) {
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(); }
108 scoped_ptr
<internal::SystemTrayBubble
> bubble_
;
109 scoped_ptr
<internal::TrayBubbleWrapper
> bubble_wrapper_
;
111 DISALLOW_COPY_AND_ASSIGN(SystemBubbleWrapper
);
114 } // namespace internal
118 using internal::SystemTrayBubble
;
120 SystemTray::SystemTray(internal::StatusAreaWidget
* status_area_widget
)
121 : internal::TrayBackgroundView(status_area_widget
),
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();
136 (*it
)->DestroyTrayView();
140 void SystemTray::InitializeTrayItems(SystemTrayDelegate
* delegate
) {
141 internal::TrayBackgroundView::Initialize();
142 CreateItems(delegate
);
145 void SystemTray::CreateItems(SystemTrayDelegate
* delegate
) {
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
));
161 #if defined(OS_CHROMEOS)
162 AddTrayItem(new internal::TrayEnterprise(this));
163 AddTrayItem(new internal::TrayLocallyManagedUser(this));
165 AddTrayItem(new internal::TrayIME(this));
166 tray_accessibility_
= new internal::TrayAccessibility(this);
167 AddTrayItem(tray_accessibility_
);
168 #if defined(OS_CHROMEOS)
170 new internal::TrayPower(this, message_center::MessageCenter::Get()));
172 #if defined(OS_CHROMEOS)
173 AddTrayItem(new internal::TrayNetwork(this));
174 AddTrayItem(new internal::TrayVPN(this));
175 AddTrayItem(new internal::TraySms(this));
178 AddTrayItem(new internal::TrayBluetooth(this));
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));
189 AddTrayItem(new internal::TrayBrightness(this));
190 AddTrayItem(new internal::TrayCapsLock(this));
192 #if defined(OS_CHROMEOS)
193 AddTrayItem(new internal::TraySettings(this));
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));
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());
217 tray_container()->AddChildViewAt(tray_item
, 0);
218 PreferredSizeChanged();
219 tray_item_map_
[item
] = tray_item
;
223 void SystemTray::RemoveTrayItem(SystemTrayItem
* item
) {
227 const std::vector
<SystemTrayItem
*>& SystemTray::GetTrayItems() const {
231 void SystemTray::ShowDefaultView(BubbleCreationType creation_type
) {
232 ShowDefaultViewWithOffset(creation_type
,
233 TrayBubbleView::InitParams::kArrowDefaultOffset
);
236 void SystemTray::ShowDetailedView(SystemTrayItem
* item
,
239 BubbleCreationType creation_type
) {
240 std::vector
<SystemTrayItem
*> items
;
241 items
.push_back(item
);
242 ShowItems(items
, true, activate
, creation_type
, GetTrayXOffset(item
));
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_
)
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())
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())
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();
285 (*it
)->UpdateAfterLoginStatusChange(login_status
);
289 PreferredSizeChanged();
292 void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment
) {
293 for (std::vector
<SystemTrayItem
*>::iterator it
= items_
.begin();
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() {
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_
)
338 return notification_bubble_
->bubble_view()->GetBoundsInScreen().Contains(
339 Shell::GetScreen()->GetCursorScreenPoint());
342 bool SystemTray::CloseSystemBubble() const {
345 system_bubble_
->bubble()->Close();
349 bool SystemTray::CloseNotificationBubbleForTest() const {
350 if (!notification_bubble_
)
352 notification_bubble_
->bubble()->Close();
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
);
396 void SystemTray::ShowDefaultViewWithOffset(BubbleCreationType creation_type
,
398 ShowItems(items_
.get(), false, true, creation_type
, arrow_offset
);
401 void SystemTray::ShowItems(const std::vector
<SystemTrayItem
*>& items
,
404 BubbleCreationType creation_type
,
406 // No system tray bubbles in kiosk mode.
407 if (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus() ==
408 ash::user::LOGGED_IN_KIOSK_APP
) {
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
);
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(),
432 init_params
.can_activate
= can_activate
;
434 // This is the case where a volume control or brightness control bubble
436 init_params
.max_height
= default_bubble_height_
;
437 init_params
.arrow_color
= kBackgroundColor
;
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.
453 default_bubble_height_
= system_bubble_
->bubble_view()->height();
455 if (detailed
&& items
.size() > 0)
456 detailed_item_
= items
[0];
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();
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
);
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
;
488 anchor
= tray_container();
489 anchor_type
= TrayBubbleView::ANCHOR_TYPE_TRAY
;
491 TrayBubbleView::InitParams
init_params(anchor_type
,
492 GetAnchorAlignment(),
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();
506 if (hide_notifications_
)
507 notification_bubble
->SetVisible(false);
509 status_area_widget()->SetHideWebNotifications(true);
512 void SystemTray::SetShelfAlignment(ShelfAlignment alignment
) {
513 if (alignment
== shelf_alignment())
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() {
558 HideBubbleWithView(system_bubble_
->bubble_view());
562 void SystemTray::BubbleViewDestroyed() {
563 if (system_bubble_
) {
564 system_bubble_
->bubble()->DestroyItemViews();
565 system_bubble_
->bubble()->BubbleViewDestroyed();
569 void SystemTray::OnMouseEnteredView() {
571 system_bubble_
->bubble()->StopAutoCloseTimer();
574 void SystemTray::OnMouseExitedView() {
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();
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
);