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"
9 #include "ash/shell/panel_window.h"
10 #include "ash/shell_window_ids.h"
11 #include "ash/system/audio/tray_volume.h"
12 #include "ash/system/bluetooth/tray_bluetooth.h"
13 #include "ash/system/brightness/tray_brightness.h"
14 #include "ash/system/chromeos/tray_display.h"
15 #include "ash/system/date/tray_date.h"
16 #include "ash/system/drive/tray_drive.h"
17 #include "ash/system/ime/tray_ime.h"
18 #include "ash/system/locale/tray_locale.h"
19 #include "ash/system/logout_button/tray_logout_button.h"
20 #include "ash/system/monitor/tray_monitor.h"
21 #include "ash/system/power/power_supply_status.h"
22 #include "ash/system/power/tray_power.h"
23 #include "ash/system/session_length_limit/tray_session_length_limit.h"
24 #include "ash/system/settings/tray_settings.h"
25 #include "ash/system/status_area_widget.h"
26 #include "ash/system/tray/system_tray_delegate.h"
27 #include "ash/system/tray/system_tray_item.h"
28 #include "ash/system/tray/tray_bubble_wrapper.h"
29 #include "ash/system/tray/tray_constants.h"
30 #include "ash/system/tray_accessibility.h"
31 #include "ash/system/tray_caps_lock.h"
32 #include "ash/system/tray_update.h"
33 #include "ash/system/user/login_status.h"
34 #include "ash/system/user/tray_user.h"
35 #include "ash/wm/shelf_layout_manager.h"
36 #include "base/command_line.h"
37 #include "base/logging.h"
38 #include "base/timer.h"
39 #include "base/utf_string_conversions.h"
40 #include "grit/ash_strings.h"
41 #include "ui/aura/root_window.h"
42 #include "ui/base/events/event_constants.h"
43 #include "ui/base/l10n/l10n_util.h"
44 #include "ui/compositor/layer.h"
45 #include "ui/gfx/canvas.h"
46 #include "ui/gfx/screen.h"
47 #include "ui/gfx/skia_util.h"
48 #include "ui/views/border.h"
49 #include "ui/views/controls/label.h"
50 #include "ui/views/layout/box_layout.h"
51 #include "ui/views/layout/fill_layout.h"
52 #include "ui/views/view.h"
54 #if defined(OS_CHROMEOS)
55 #include "ash/system/chromeos/network/tray_network.h"
56 #include "ash/system/chromeos/network/tray_sms.h"
57 #include "ash/system/chromeos/network/tray_vpn.h"
60 using views::TrayBubbleView
;
66 // Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper
67 // instances for a bubble.
69 class SystemBubbleWrapper
{
71 // Takes ownership of |bubble|.
72 explicit SystemBubbleWrapper(internal::SystemTrayBubble
* bubble
)
76 // Initializes the bubble view and creates |bubble_wrapper_|.
77 void InitView(TrayBackgroundView
* tray
,
79 TrayBubbleView::InitParams
* init_params
) {
80 user::LoginStatus login_status
=
81 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
82 bubble_
->InitView(anchor
, login_status
, init_params
);
83 bubble_wrapper_
.reset(
84 new internal::TrayBubbleWrapper(tray
, bubble_
->bubble_view()));
87 // Convenience accessors:
88 SystemTrayBubble
* bubble() const { return bubble_
.get(); }
89 SystemTrayBubble::BubbleType
bubble_type() const {
90 return bubble_
->bubble_type();
92 TrayBubbleView
* bubble_view() const { return bubble_
->bubble_view(); }
95 scoped_ptr
<internal::SystemTrayBubble
> bubble_
;
96 scoped_ptr
<internal::TrayBubbleWrapper
> bubble_wrapper_
;
98 DISALLOW_COPY_AND_ASSIGN(SystemBubbleWrapper
);
101 } // namespace internal
105 using internal::SystemTrayBubble
;
107 SystemTray::SystemTray(internal::StatusAreaWidget
* status_area_widget
)
108 : internal::TrayBackgroundView(status_area_widget
),
110 default_bubble_height_(0),
111 hide_notifications_(false) {
112 SetContentsBackground();
115 SystemTray::~SystemTray() {
116 // Destroy any child views that might have back pointers before ~View().
117 system_bubble_
.reset();
118 notification_bubble_
.reset();
119 for (std::vector
<SystemTrayItem
*>::iterator it
= items_
.begin();
122 (*it
)->DestroyTrayView();
126 void SystemTray::InitializeTrayItems(SystemTrayDelegate
* delegate
) {
127 internal::TrayBackgroundView::Initialize();
128 CreateItems(delegate
);
131 void SystemTray::CreateItems(SystemTrayDelegate
* delegate
) {
132 AddTrayItem(new internal::TraySessionLengthLimit(this));
133 AddTrayItem(new internal::TrayLogoutButton(this));
134 AddTrayItem(new internal::TrayUser(this));
135 AddTrayItem(new internal::TrayIME(this));
136 tray_accessibility_
= new internal::TrayAccessibility(this);
137 AddTrayItem(tray_accessibility_
);
138 AddTrayItem(new internal::TrayPower(this));
139 #if defined(OS_CHROMEOS)
140 AddTrayItem(new internal::TrayNetwork(this));
141 AddTrayItem(new internal::TrayVPN(this));
142 AddTrayItem(new internal::TraySms(this));
144 AddTrayItem(new internal::TrayBluetooth(this));
145 AddTrayItem(new internal::TrayDrive(this));
146 AddTrayItem(new internal::TrayLocale(this));
147 #if defined(OS_CHROMEOS)
148 AddTrayItem(new internal::TrayDisplay(this));
150 AddTrayItem(new internal::TrayVolume(this));
151 AddTrayItem(new internal::TrayBrightness(this));
152 AddTrayItem(new internal::TrayUpdate(this));
153 AddTrayItem(new internal::TrayCapsLock(this));
154 AddTrayItem(new internal::TraySettings(this));
155 AddTrayItem(new internal::TrayDate(this));
157 #if defined(OS_LINUX)
158 // Add memory monitor if enabled.
159 CommandLine
* cmd
= CommandLine::ForCurrentProcess();
160 if (cmd
->HasSwitch(ash::switches::kAshEnableMemoryMonitor
))
161 AddTrayItem(new internal::TrayMonitor(this));
164 SetVisible(ash::Shell::GetInstance()->system_tray_delegate()->
165 GetTrayVisibilityOnStartup());
168 void SystemTray::AddTrayItem(SystemTrayItem
* item
) {
169 items_
.push_back(item
);
171 SystemTrayDelegate
* delegate
= Shell::GetInstance()->system_tray_delegate();
172 views::View
* tray_item
= item
->CreateTrayView(delegate
->GetUserLoginStatus());
173 item
->UpdateAfterShelfAlignmentChange(shelf_alignment());
176 tray_container()->AddChildViewAt(tray_item
, 0);
177 PreferredSizeChanged();
178 tray_item_map_
[item
] = tray_item
;
182 void SystemTray::RemoveTrayItem(SystemTrayItem
* item
) {
186 void SystemTray::ShowDefaultView(BubbleCreationType creation_type
) {
187 ShowDefaultViewWithOffset(creation_type
,
188 TrayBubbleView::InitParams::kArrowDefaultOffset
);
191 void SystemTray::ShowDetailedView(SystemTrayItem
* item
,
194 BubbleCreationType creation_type
) {
195 std::vector
<SystemTrayItem
*> items
;
196 items
.push_back(item
);
197 ShowItems(items
, true, activate
, creation_type
, GetTrayXOffset(item
));
198 if (system_bubble_
.get())
199 system_bubble_
->bubble()->StartAutoCloseTimer(close_delay
);
202 void SystemTray::SetDetailedViewCloseDelay(int close_delay
) {
203 if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DETAILED
))
204 system_bubble_
->bubble()->StartAutoCloseTimer(close_delay
);
207 void SystemTray::HideDetailedView(SystemTrayItem
* item
) {
208 if (item
!= detailed_item_
)
210 DestroySystemBubble();
211 UpdateNotificationBubble();
214 void SystemTray::ShowNotificationView(SystemTrayItem
* item
) {
215 if (std::find(notification_items_
.begin(), notification_items_
.end(), item
)
216 != notification_items_
.end())
218 notification_items_
.push_back(item
);
219 UpdateNotificationBubble();
222 void SystemTray::HideNotificationView(SystemTrayItem
* item
) {
223 std::vector
<SystemTrayItem
*>::iterator found_iter
=
224 std::find(notification_items_
.begin(), notification_items_
.end(), item
);
225 if (found_iter
== notification_items_
.end())
227 notification_items_
.erase(found_iter
);
228 // Only update the notification bubble if visible (i.e. don't create one).
229 if (notification_bubble_
.get())
230 UpdateNotificationBubble();
233 void SystemTray::UpdateAfterLoginStatusChange(user::LoginStatus login_status
) {
234 DestroySystemBubble();
236 for (std::vector
<SystemTrayItem
*>::iterator it
= items_
.begin();
239 (*it
)->UpdateAfterLoginStatusChange(login_status
);
243 PreferredSizeChanged();
246 void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment
) {
247 for (std::vector
<SystemTrayItem
*>::iterator it
= items_
.begin();
250 (*it
)->UpdateAfterShelfAlignmentChange(alignment
);
254 void SystemTray::SetHideNotifications(bool hide_notifications
) {
255 if (notification_bubble_
.get())
256 notification_bubble_
->bubble()->SetVisible(!hide_notifications
);
257 hide_notifications_
= hide_notifications
;
260 bool SystemTray::ShouldShowLauncher() const {
261 return system_bubble_
.get() && system_bubble_
->bubble()->ShouldShowLauncher();
264 bool SystemTray::HasSystemBubble() const {
265 return system_bubble_
.get() != NULL
;
268 internal::SystemTrayBubble
* SystemTray::GetSystemBubble() {
269 if (!system_bubble_
.get())
271 return system_bubble_
->bubble();
274 bool SystemTray::IsAnyBubbleVisible() const {
275 return ((system_bubble_
.get() &&
276 system_bubble_
->bubble()->IsVisible()) ||
277 (notification_bubble_
.get() &&
278 notification_bubble_
->bubble()->IsVisible()));
281 bool SystemTray::IsMouseInNotificationBubble() const {
282 if (!notification_bubble_
.get())
284 return notification_bubble_
->bubble_view()->GetBoundsInScreen().Contains(
285 Shell::GetScreen()->GetCursorScreenPoint());
288 bool SystemTray::CloseBubbleForTest() const {
289 if (!system_bubble_
.get())
291 system_bubble_
->bubble()->Close();
297 bool SystemTray::HasSystemBubbleType(SystemTrayBubble::BubbleType type
) {
298 DCHECK(type
!= SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION
);
299 return system_bubble_
.get() && system_bubble_
->bubble_type() == type
;
302 void SystemTray::DestroySystemBubble() {
303 system_bubble_
.reset();
304 detailed_item_
= NULL
;
307 void SystemTray::DestroyNotificationBubble() {
308 notification_bubble_
.reset();
309 status_area_widget()->SetHideWebNotifications(false);
312 int SystemTray::GetTrayXOffset(SystemTrayItem
* item
) const {
313 // Don't attempt to align the arrow if the shelf is on the left or right.
314 if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM
)
315 return TrayBubbleView::InitParams::kArrowDefaultOffset
;
317 std::map
<SystemTrayItem
*, views::View
*>::const_iterator it
=
318 tray_item_map_
.find(item
);
319 if (it
== tray_item_map_
.end())
320 return TrayBubbleView::InitParams::kArrowDefaultOffset
;
322 const views::View
* item_view
= it
->second
;
323 if (item_view
->bounds().IsEmpty()) {
324 // The bounds of item could be still empty if it does not have a visible
325 // tray view. In that case, use the default (minimum) offset.
326 return TrayBubbleView::InitParams::kArrowDefaultOffset
;
329 gfx::Point
point(item_view
->width() / 2, 0);
330 ConvertPointToWidget(item_view
, &point
);
334 void SystemTray::ShowDefaultViewWithOffset(BubbleCreationType creation_type
,
336 ShowItems(items_
.get(), false, true, creation_type
, arrow_offset
);
339 void SystemTray::ShowItems(const std::vector
<SystemTrayItem
*>& items
,
342 BubbleCreationType creation_type
,
344 // Destroy any existing bubble and create a new one.
345 SystemTrayBubble::BubbleType bubble_type
= detailed
?
346 SystemTrayBubble::BUBBLE_TYPE_DETAILED
:
347 SystemTrayBubble::BUBBLE_TYPE_DEFAULT
;
349 // Destroy the notification bubble here so that it doesn't get rebuilt
350 // while we add items to the main bubble_ (e.g. in HideNotificationView).
351 notification_bubble_
.reset();
353 if (system_bubble_
.get() && creation_type
== BUBBLE_USE_EXISTING
) {
354 system_bubble_
->bubble()->UpdateView(items
, bubble_type
);
356 TrayBubbleView::InitParams
init_params(TrayBubbleView::ANCHOR_TYPE_TRAY
,
357 GetAnchorAlignment(),
360 init_params
.can_activate
= can_activate
;
362 // This is the case where a volume control or brightness control bubble
364 init_params
.max_height
= default_bubble_height_
;
365 init_params
.arrow_color
= kBackgroundColor
;
367 init_params
.arrow_color
= kHeaderBackgroundColorDark
;
369 init_params
.arrow_offset
= arrow_offset
;
370 SystemTrayBubble
* bubble
= new SystemTrayBubble(this, items
, bubble_type
);
371 system_bubble_
.reset(new internal::SystemBubbleWrapper(bubble
));
372 system_bubble_
->InitView(this, tray_container(), &init_params
);
374 // Save height of default view for creating detailed views directly.
376 default_bubble_height_
= system_bubble_
->bubble_view()->height();
378 if (detailed
&& items
.size() > 0)
379 detailed_item_
= items
[0];
381 detailed_item_
= NULL
;
383 UpdateNotificationBubble(); // State changed, re-create notifications.
384 status_area_widget()->SetHideWebNotifications(true);
385 GetShelfLayoutManager()->UpdateAutoHideState();
388 void SystemTray::UpdateNotificationBubble() {
389 // Only show the notification buble if we have notifications and we are not
390 // showing the default bubble.
391 if (notification_items_
.empty() ||
392 HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DEFAULT
)) {
393 DestroyNotificationBubble();
396 // Destroy the existing bubble before constructing a new one.
397 notification_bubble_
.reset();
398 SystemTrayBubble
* notification_bubble
;
399 if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DETAILED
)) {
400 // Skip notifications for any currently displayed detailed item.
401 std::vector
<SystemTrayItem
*> items
;
402 for (std::vector
<SystemTrayItem
*>::iterator iter
=
403 notification_items_
.begin();
404 iter
!= notification_items_
.end(); ++ iter
) {
405 if (*iter
!= detailed_item_
)
406 items
.push_back(*iter
);
409 DestroyNotificationBubble();
412 notification_bubble
= new SystemTrayBubble(
413 this, items
, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION
);
415 // Show all notifications.
416 notification_bubble
= new SystemTrayBubble(
417 this, notification_items_
, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION
);
420 TrayBubbleView::AnchorType anchor_type
;
421 if (system_bubble_
.get()) {
422 anchor
= system_bubble_
->bubble_view();
423 anchor_type
= TrayBubbleView::ANCHOR_TYPE_BUBBLE
;
425 anchor
= tray_container();
426 anchor_type
= TrayBubbleView::ANCHOR_TYPE_TRAY
;
428 TrayBubbleView::InitParams
init_params(anchor_type
,
429 GetAnchorAlignment(),
432 init_params
.arrow_color
= kBackgroundColor
;
433 init_params
.arrow_offset
= GetTrayXOffset(notification_items_
[0]);
434 notification_bubble_
.reset(
435 new internal::SystemBubbleWrapper(notification_bubble
));
436 notification_bubble_
->InitView(this, anchor
, &init_params
);
438 if (notification_bubble
->bubble_view()->child_count() == 0) {
439 // It is possible that none of the items generated actual notifications.
440 DestroyNotificationBubble();
443 if (hide_notifications_
)
444 notification_bubble
->SetVisible(false);
446 status_area_widget()->SetHideWebNotifications(true);
449 void SystemTray::SetShelfAlignment(ShelfAlignment alignment
) {
450 if (alignment
== shelf_alignment())
452 internal::TrayBackgroundView::SetShelfAlignment(alignment
);
453 UpdateAfterShelfAlignmentChange(alignment
);
454 // Destroy any existing bubble so that it is rebuilt correctly.
455 system_bubble_
.reset();
456 // Rebuild any notification bubble.
457 if (notification_bubble_
.get()) {
458 notification_bubble_
.reset();
459 UpdateNotificationBubble();
463 void SystemTray::AnchorUpdated() {
464 if (notification_bubble_
.get()) {
465 notification_bubble_
->bubble_view()->UpdateBubble();
466 // Ensure that the notification buble is above the launcher/status area.
467 notification_bubble_
->bubble_view()->GetWidget()->StackAtTop();
468 UpdateBubbleViewArrow(notification_bubble_
->bubble_view());
470 if (system_bubble_
.get()) {
471 system_bubble_
->bubble_view()->UpdateBubble();
472 UpdateBubbleViewArrow(system_bubble_
->bubble_view());
476 string16
SystemTray::GetAccessibleNameForTray() {
477 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME
);
480 void SystemTray::HideBubbleWithView(const TrayBubbleView
* bubble_view
) {
481 if (system_bubble_
.get() && bubble_view
== system_bubble_
->bubble_view()) {
482 DestroySystemBubble();
483 UpdateNotificationBubble(); // State changed, re-create notifications.
484 GetShelfLayoutManager()->UpdateAutoHideState();
485 } else if (notification_bubble_
.get() &&
486 bubble_view
== notification_bubble_
->bubble_view()) {
487 DestroyNotificationBubble();
491 bool SystemTray::ClickedOutsideBubble() {
492 if (!system_bubble_
.get())
494 HideBubbleWithView(system_bubble_
->bubble_view());
498 void SystemTray::BubbleViewDestroyed() {
499 if (system_bubble_
.get()) {
500 system_bubble_
->bubble()->DestroyItemViews();
501 system_bubble_
->bubble()->BubbleViewDestroyed();
505 void SystemTray::OnMouseEnteredView() {
506 if (system_bubble_
.get())
507 system_bubble_
->bubble()->StopAutoCloseTimer();
510 void SystemTray::OnMouseExitedView() {
511 if (system_bubble_
.get())
512 system_bubble_
->bubble()->RestartAutoCloseTimer();
515 string16
SystemTray::GetAccessibleNameForBubble() {
516 return GetAccessibleNameForTray();
519 gfx::Rect
SystemTray::GetAnchorRect(
520 views::Widget
* anchor_widget
,
521 TrayBubbleView::AnchorType anchor_type
,
522 TrayBubbleView::AnchorAlignment anchor_alignment
) {
523 return GetBubbleAnchorRect(anchor_widget
, anchor_type
, anchor_alignment
);
526 void SystemTray::HideBubble(const TrayBubbleView
* bubble_view
) {
527 HideBubbleWithView(bubble_view
);
530 bool SystemTray::PerformAction(const ui::Event
& event
) {
531 // If we're already showing the default view, hide it; otherwise, show it
532 // (and hide any popup that's currently shown).
533 if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DEFAULT
)) {
534 system_bubble_
->bubble()->Close();
536 int arrow_offset
= TrayBubbleView::InitParams::kArrowDefaultOffset
;
537 if (event
.IsMouseEvent() || event
.type() == ui::ET_GESTURE_TAP
) {
538 const ui::LocatedEvent
& located_event
=
539 static_cast<const ui::LocatedEvent
&>(event
);
540 if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM
) {
541 gfx::Point
point(located_event
.x(), 0);
542 ConvertPointToWidget(this, &point
);
543 arrow_offset
= point
.x();
546 ShowDefaultViewWithOffset(BUBBLE_CREATE_NEW
, arrow_offset
);