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.
4
5#include "ash/system/tray/system_tray.h"
6
7#include "ash/ash_switches.h"
8#include "ash/metrics/user_metrics_recorder.h"
9#include "ash/shelf/shelf_layout_manager.h"
10#include "ash/shell.h"
11#include "ash/shell_window_ids.h"
12#include "ash/system/audio/tray_audio.h"
13#include "ash/system/bluetooth/tray_bluetooth.h"
14#include "ash/system/date/tray_date.h"
15#include "ash/system/ime/tray_ime.h"
16#include "ash/system/status_area_widget.h"
17#include "ash/system/tray/system_tray_delegate.h"
18#include "ash/system/tray/system_tray_item.h"
19#include "ash/system/tray/tray_bubble_wrapper.h"
20#include "ash/system/tray/tray_constants.h"
21#include "ash/system/tray_accessibility.h"
22#include "ash/system/tray_update.h"
23#include "ash/system/user/login_status.h"
24#include "ash/system/user/tray_user.h"
25#include "ash/system/user/tray_user_separator.h"
26#include "ash/system/web_notification/web_notification_tray.h"
27#include "base/logging.h"
28#include "base/strings/utf_string_conversions.h"
29#include "base/timer/timer.h"
30#include "grit/ash_strings.h"
31#include "ui/aura/window_event_dispatcher.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "ui/compositor/layer.h"
34#include "ui/events/event_constants.h"
35#include "ui/gfx/canvas.h"
36#include "ui/gfx/screen.h"
37#include "ui/gfx/skia_util.h"
38#include "ui/views/border.h"
39#include "ui/views/controls/label.h"
40#include "ui/views/layout/box_layout.h"
41#include "ui/views/layout/fill_layout.h"
42#include "ui/views/view.h"
43
44#if defined(OS_CHROMEOS)
45#include "ash/system/chromeos/audio/tray_audio_chromeos.h"
46#include "ash/system/chromeos/brightness/tray_brightness.h"
47#include "ash/system/chromeos/enterprise/tray_enterprise.h"
48#include "ash/system/chromeos/network/tray_network.h"
49#include "ash/system/chromeos/network/tray_sms.h"
50#include "ash/system/chromeos/network/tray_vpn.h"
51#include "ash/system/chromeos/power/power_status.h"
52#include "ash/system/chromeos/power/tray_power.h"
53#include "ash/system/chromeos/rotation/tray_rotation_lock.h"
54#include "ash/system/chromeos/screen_security/screen_capture_tray_item.h"
55#include "ash/system/chromeos/screen_security/screen_share_tray_item.h"
56#include "ash/system/chromeos/session/tray_session_length_limit.h"
57#include "ash/system/chromeos/settings/tray_settings.h"
58#include "ash/system/chromeos/supervised/tray_supervised_user.h"
59#include "ash/system/chromeos/tray_caps_lock.h"
60#include "ash/system/chromeos/tray_display.h"
61#include "ash/system/chromeos/tray_tracing.h"
62#include "ash/system/tray/media_security/multi_profile_media_tray_item.h"
63#include "ui/message_center/message_center.h"
64#elif defined(OS_WIN)
65#include "ash/system/win/audio/tray_audio_win.h"
66#include "media/audio/win/core_audio_util_win.h"
67#endif
68
69using views::TrayBubbleView;
70
71namespace ash {
72
73// The minimum width of the system tray menu width.
74const int kMinimumSystemTrayMenuWidth = 300;
75
76// Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper
77// instances for a bubble.
78
79class SystemBubbleWrapper {
80 public:
81  // Takes ownership of |bubble|.
82  explicit SystemBubbleWrapper(SystemTrayBubble* bubble)
83      : bubble_(bubble), is_persistent_(false) {}
84
85  // Initializes the bubble view and creates |bubble_wrapper_|.
86  void InitView(TrayBackgroundView* tray,
87                views::View* anchor,
88                TrayBubbleView::InitParams* init_params,
89                bool is_persistent) {
90    DCHECK(anchor);
91    user::LoginStatus login_status =
92        Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
93    bubble_->InitView(anchor, login_status, init_params);
94    bubble_wrapper_.reset(new TrayBubbleWrapper(tray, bubble_->bubble_view()));
95    // The system bubble should not have an arrow.
96    bubble_->bubble_view()->SetArrowPaintType(
97        views::BubbleBorder::PAINT_NONE);
98    is_persistent_ = is_persistent;
99
100    // If ChromeVox is enabled, focus the default item if no item is focused.
101    if (Shell::GetInstance()->accessibility_delegate()->
102        IsSpokenFeedbackEnabled()) {
103      bubble_->FocusDefaultIfNeeded();
104    }
105  }
106
107  // Convenience accessors:
108  SystemTrayBubble* bubble() const { return bubble_.get(); }
109  SystemTrayBubble::BubbleType bubble_type() const {
110    return bubble_->bubble_type();
111  }
112  TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }
113  bool is_persistent() const { return is_persistent_; }
114
115 private:
116  scoped_ptr<SystemTrayBubble> bubble_;
117  scoped_ptr<TrayBubbleWrapper> bubble_wrapper_;
118  bool is_persistent_;
119
120  DISALLOW_COPY_AND_ASSIGN(SystemBubbleWrapper);
121};
122
123
124// SystemTray
125
126SystemTray::SystemTray(StatusAreaWidget* status_area_widget)
127    : TrayBackgroundView(status_area_widget),
128      items_(),
129      default_bubble_height_(0),
130      hide_notifications_(false),
131      full_system_tray_menu_(false),
132      tray_accessibility_(NULL),
133      tray_date_(NULL) {
134  SetContentsBackground();
135}
136
137SystemTray::~SystemTray() {
138  // Destroy any child views that might have back pointers before ~View().
139  system_bubble_.reset();
140  notification_bubble_.reset();
141  for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
142       it != items_.end();
143       ++it) {
144    (*it)->DestroyTrayView();
145  }
146}
147
148void SystemTray::InitializeTrayItems(SystemTrayDelegate* delegate) {
149  TrayBackgroundView::Initialize();
150  CreateItems(delegate);
151}
152
153void SystemTray::CreateItems(SystemTrayDelegate* delegate) {
154#if !defined(OS_WIN)
155  // Create user items for each possible user.
156  ash::Shell* shell = ash::Shell::GetInstance();
157  int maximum_user_profiles =
158          shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers();
159  for (int i = 0; i < maximum_user_profiles; i++)
160    AddTrayItem(new TrayUser(this, i));
161
162  if (maximum_user_profiles > 1) {
163    // Add a special double line separator between users and the rest of the
164    // menu if more then one user is logged in.
165    AddTrayItem(new TrayUserSeparator(this));
166  }
167#endif
168
169  tray_accessibility_ = new TrayAccessibility(this);
170  tray_date_ = new TrayDate(this);
171
172#if defined(OS_CHROMEOS)
173  AddTrayItem(new TraySessionLengthLimit(this));
174  AddTrayItem(new TrayEnterprise(this));
175  AddTrayItem(new TraySupervisedUser(this));
176  AddTrayItem(new TrayIME(this));
177  AddTrayItem(tray_accessibility_);
178  AddTrayItem(new TrayTracing(this));
179  AddTrayItem(new TrayPower(this, message_center::MessageCenter::Get()));
180  AddTrayItem(new TrayNetwork(this));
181  AddTrayItem(new TrayVPN(this));
182  AddTrayItem(new TraySms(this));
183  AddTrayItem(new TrayBluetooth(this));
184  AddTrayItem(new TrayDisplay(this));
185  screen_capture_tray_item_ = new ScreenCaptureTrayItem(this);
186  AddTrayItem(screen_capture_tray_item_);
187  screen_share_tray_item_ = new ScreenShareTrayItem(this);
188  AddTrayItem(screen_share_tray_item_);
189  AddTrayItem(new MultiProfileMediaTrayItem(this));
190  AddTrayItem(new TrayAudioChromeOs(this));
191  AddTrayItem(new TrayBrightness(this));
192  AddTrayItem(new TrayCapsLock(this));
193  AddTrayItem(new TraySettings(this));
194  AddTrayItem(new TrayUpdate(this));
195  AddTrayItem(new TrayRotationLock(this));
196  AddTrayItem(tray_date_);
197#elif defined(OS_WIN)
198  AddTrayItem(tray_accessibility_);
199  if (media::CoreAudioUtil::IsSupported())
200    AddTrayItem(new TrayAudioWin(this));
201  AddTrayItem(new TrayUpdate(this));
202  AddTrayItem(tray_date_);
203#elif defined(OS_LINUX)
204  AddTrayItem(new TrayIME(this));
205  AddTrayItem(tray_accessibility_);
206  AddTrayItem(new TrayBluetooth(this));
207  AddTrayItem(new TrayUpdate(this));
208  AddTrayItem(tray_date_);
209#endif
210
211  SetVisible(ash::Shell::GetInstance()->system_tray_delegate()->
212      GetTrayVisibilityOnStartup());
213}
214
215void SystemTray::AddTrayItem(SystemTrayItem* item) {
216  items_.push_back(item);
217
218  SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
219  views::View* tray_item = item->CreateTrayView(delegate->GetUserLoginStatus());
220  item->UpdateAfterShelfAlignmentChange(shelf_alignment());
221
222  if (tray_item) {
223    tray_container()->AddChildViewAt(tray_item, 0);
224    PreferredSizeChanged();
225    tray_item_map_[item] = tray_item;
226  }
227}
228
229void SystemTray::RemoveTrayItem(SystemTrayItem* item) {
230  NOTIMPLEMENTED();
231}
232
233const std::vector<SystemTrayItem*>& SystemTray::GetTrayItems() const {
234  return items_.get();
235}
236
237void SystemTray::ShowDefaultView(BubbleCreationType creation_type) {
238  ShowDefaultViewWithOffset(
239      creation_type,
240      TrayBubbleView::InitParams::kArrowDefaultOffset,
241      false);
242}
243
244void SystemTray::ShowPersistentDefaultView() {
245  ShowItems(items_.get(),
246            false,
247            false,
248            BUBBLE_CREATE_NEW,
249            TrayBubbleView::InitParams::kArrowDefaultOffset,
250            true);
251}
252
253void SystemTray::ShowDetailedView(SystemTrayItem* item,
254                                  int close_delay,
255                                  bool activate,
256                                  BubbleCreationType creation_type) {
257  std::vector<SystemTrayItem*> items;
258  // The detailed view with timeout means a UI to show the current system state,
259  // like the audio level or brightness. Such UI should behave as persistent and
260  // keep its own logic for the appearance.
261  bool persistent = (
262      !activate && close_delay > 0 && creation_type == BUBBLE_CREATE_NEW);
263  items.push_back(item);
264  ShowItems(
265      items, true, activate, creation_type, GetTrayXOffset(item), persistent);
266  if (system_bubble_)
267    system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
268}
269
270void SystemTray::SetDetailedViewCloseDelay(int close_delay) {
271  if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DETAILED))
272    system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
273}
274
275void SystemTray::HideDetailedView(SystemTrayItem* item) {
276  if (item != detailed_item_)
277    return;
278  DestroySystemBubble();
279  UpdateNotificationBubble();
280}
281
282void SystemTray::ShowNotificationView(SystemTrayItem* item) {
283  if (std::find(notification_items_.begin(), notification_items_.end(), item)
284      != notification_items_.end())
285    return;
286  notification_items_.push_back(item);
287  UpdateNotificationBubble();
288}
289
290void SystemTray::HideNotificationView(SystemTrayItem* item) {
291  std::vector<SystemTrayItem*>::iterator found_iter =
292      std::find(notification_items_.begin(), notification_items_.end(), item);
293  if (found_iter == notification_items_.end())
294    return;
295  notification_items_.erase(found_iter);
296  // Only update the notification bubble if visible (i.e. don't create one).
297  if (notification_bubble_)
298    UpdateNotificationBubble();
299}
300
301void SystemTray::UpdateAfterLoginStatusChange(user::LoginStatus login_status) {
302  DestroySystemBubble();
303  UpdateNotificationBubble();
304
305  for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
306      it != items_.end();
307      ++it) {
308    (*it)->UpdateAfterLoginStatusChange(login_status);
309  }
310
311  // Items default to SHELF_ALIGNMENT_BOTTOM. Update them if the initial
312  // position of the shelf differs.
313  if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM)
314    UpdateAfterShelfAlignmentChange(shelf_alignment());
315
316  SetVisible(true);
317  PreferredSizeChanged();
318}
319
320void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
321  for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
322      it != items_.end();
323      ++it) {
324    (*it)->UpdateAfterShelfAlignmentChange(alignment);
325  }
326}
327
328void SystemTray::SetHideNotifications(bool hide_notifications) {
329  if (notification_bubble_)
330    notification_bubble_->bubble()->SetVisible(!hide_notifications);
331  hide_notifications_ = hide_notifications;
332}
333
334bool SystemTray::ShouldShowShelf() const {
335  return system_bubble_.get() && system_bubble_->bubble()->ShouldShowShelf();
336}
337
338bool SystemTray::HasSystemBubble() const {
339  return system_bubble_.get() != NULL;
340}
341
342bool SystemTray::HasNotificationBubble() const {
343  return notification_bubble_.get() != NULL;
344}
345
346SystemTrayBubble* SystemTray::GetSystemBubble() {
347  if (!system_bubble_)
348    return NULL;
349  return system_bubble_->bubble();
350}
351
352bool SystemTray::IsAnyBubbleVisible() const {
353  return ((system_bubble_.get() &&
354           system_bubble_->bubble()->IsVisible()) ||
355          (notification_bubble_.get() &&
356           notification_bubble_->bubble()->IsVisible()));
357}
358
359bool SystemTray::IsMouseInNotificationBubble() const {
360  if (!notification_bubble_)
361    return false;
362  return notification_bubble_->bubble_view()->GetBoundsInScreen().Contains(
363      Shell::GetScreen()->GetCursorScreenPoint());
364}
365
366bool SystemTray::CloseSystemBubble() const {
367  if (!system_bubble_)
368    return false;
369  system_bubble_->bubble()->Close();
370  return true;
371}
372
373views::View* SystemTray::GetHelpButtonView() const {
374  return tray_date_->GetHelpButtonView();
375}
376
377bool SystemTray::CloseNotificationBubbleForTest() const {
378  if (!notification_bubble_)
379    return false;
380  notification_bubble_->bubble()->Close();
381  return true;
382}
383
384// Private methods.
385
386bool SystemTray::HasSystemBubbleType(SystemTrayBubble::BubbleType type) {
387  DCHECK(type != SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
388  return system_bubble_.get() && system_bubble_->bubble_type() == type;
389}
390
391void SystemTray::DestroySystemBubble() {
392  CloseSystemBubbleAndDeactivateSystemTray();
393  detailed_item_ = NULL;
394  UpdateWebNotifications();
395}
396
397void SystemTray::DestroyNotificationBubble() {
398  if (notification_bubble_) {
399    notification_bubble_.reset();
400    UpdateWebNotifications();
401  }
402}
403
404base::string16 SystemTray::GetAccessibleNameForTray() {
405  base::string16 time = GetAccessibleTimeString(base::Time::Now());
406  base::string16 battery = base::ASCIIToUTF16("");
407#if defined(OS_CHROMEOS)
408  battery = PowerStatus::Get()->GetAccessibleNameString(false);
409#endif
410  return l10n_util::GetStringFUTF16(
411      IDS_ASH_STATUS_TRAY_ACCESSIBLE_DESCRIPTION, time, battery);
412}
413
414int SystemTray::GetTrayXOffset(SystemTrayItem* item) const {
415  // Don't attempt to align the arrow if the shelf is on the left or right.
416  if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM &&
417      shelf_alignment() != SHELF_ALIGNMENT_TOP)
418    return TrayBubbleView::InitParams::kArrowDefaultOffset;
419
420  std::map<SystemTrayItem*, views::View*>::const_iterator it =
421      tray_item_map_.find(item);
422  if (it == tray_item_map_.end())
423    return TrayBubbleView::InitParams::kArrowDefaultOffset;
424
425  const views::View* item_view = it->second;
426  if (item_view->bounds().IsEmpty()) {
427    // The bounds of item could be still empty if it does not have a visible
428    // tray view. In that case, use the default (minimum) offset.
429    return TrayBubbleView::InitParams::kArrowDefaultOffset;
430  }
431
432  gfx::Point point(item_view->width() / 2, 0);
433  ConvertPointToWidget(item_view, &point);
434  return point.x();
435}
436
437void SystemTray::ShowDefaultViewWithOffset(BubbleCreationType creation_type,
438                                           int arrow_offset,
439                                           bool persistent) {
440  if (creation_type != BUBBLE_USE_EXISTING) {
441    Shell::GetInstance()->metrics()->RecordUserMetricsAction(
442        ash::UMA_STATUS_AREA_MENU_OPENED);
443  }
444  ShowItems(items_.get(), false, true, creation_type, arrow_offset, persistent);
445}
446
447void SystemTray::ShowItems(const std::vector<SystemTrayItem*>& items,
448                           bool detailed,
449                           bool can_activate,
450                           BubbleCreationType creation_type,
451                           int arrow_offset,
452                           bool persistent) {
453  // No system tray bubbles in kiosk mode.
454  if (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus() ==
455      ash::user::LOGGED_IN_KIOSK_APP) {
456    return;
457  }
458
459  // Destroy any existing bubble and create a new one.
460  SystemTrayBubble::BubbleType bubble_type = detailed ?
461      SystemTrayBubble::BUBBLE_TYPE_DETAILED :
462      SystemTrayBubble::BUBBLE_TYPE_DEFAULT;
463
464  // Destroy the notification bubble here so that it doesn't get rebuilt
465  // while we add items to the main bubble_ (e.g. in HideNotificationView).
466  notification_bubble_.reset();
467  if (system_bubble_.get() && creation_type == BUBBLE_USE_EXISTING) {
468    system_bubble_->bubble()->UpdateView(items, bubble_type);
469    // If ChromeVox is enabled, focus the default item if no item is focused.
470    if (Shell::GetInstance()->accessibility_delegate()->
471        IsSpokenFeedbackEnabled()) {
472      system_bubble_->bubble()->FocusDefaultIfNeeded();
473    }
474  } else {
475    // Remember if the menu is a single property (like e.g. volume) or the
476    // full tray menu. Note that in case of the |BUBBLE_USE_EXISTING| case
477    // above, |full_system_tray_menu_| does not get changed since the fact that
478    // the menu is full (or not) doesn't change even if a "single property"
479    // (like network) replaces most of the menu.
480    full_system_tray_menu_ = items.size() > 1;
481    // The menu width is fixed, and it is a per language setting.
482    int menu_width = std::max(kMinimumSystemTrayMenuWidth,
483        Shell::GetInstance()->system_tray_delegate()->GetSystemTrayMenuWidth());
484
485    TrayBubbleView::InitParams init_params(TrayBubbleView::ANCHOR_TYPE_TRAY,
486                                           GetAnchorAlignment(),
487                                           menu_width,
488                                           kTrayPopupMaxWidth);
489    init_params.can_activate = can_activate;
490    init_params.first_item_has_no_margin = true;
491    if (detailed) {
492      // This is the case where a volume control or brightness control bubble
493      // is created.
494      init_params.max_height = default_bubble_height_;
495      init_params.arrow_color = kBackgroundColor;
496    } else {
497      init_params.arrow_color = kHeaderBackgroundColor;
498    }
499    init_params.arrow_offset = arrow_offset;
500    if (bubble_type == SystemTrayBubble::BUBBLE_TYPE_DEFAULT)
501      init_params.close_on_deactivate = !persistent;
502    // For Volume and Brightness we don't want to show an arrow when
503    // they are shown in a bubble by themselves.
504    init_params.arrow_paint_type = views::BubbleBorder::PAINT_NORMAL;
505    if (items.size() == 1 && items[0]->ShouldHideArrow())
506      init_params.arrow_paint_type = views::BubbleBorder::PAINT_TRANSPARENT;
507    SystemTrayBubble* bubble = new SystemTrayBubble(this, items, bubble_type);
508    system_bubble_.reset(new SystemBubbleWrapper(bubble));
509    system_bubble_->InitView(this, tray_container(), &init_params, persistent);
510  }
511  // Save height of default view for creating detailed views directly.
512  if (!detailed)
513    default_bubble_height_ = system_bubble_->bubble_view()->height();
514
515  if (detailed && items.size() > 0)
516    detailed_item_ = items[0];
517  else
518    detailed_item_ = NULL;
519
520  UpdateNotificationBubble();  // State changed, re-create notifications.
521  if (!notification_bubble_)
522    UpdateWebNotifications();
523  GetShelfLayoutManager()->UpdateAutoHideState();
524
525  // When we show the system menu in our alternate shelf layout, we need to
526  // tint the background.
527  if (full_system_tray_menu_)
528    SetDrawBackgroundAsActive(true);
529}
530
531void SystemTray::UpdateNotificationBubble() {
532  // Only show the notification bubble if we have notifications.
533  if (notification_items_.empty()) {
534    DestroyNotificationBubble();
535    return;
536  }
537  // Destroy the existing bubble before constructing a new one.
538  notification_bubble_.reset();
539  SystemTrayBubble* notification_bubble;
540  notification_bubble = new SystemTrayBubble(
541      this, notification_items_, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
542  views::View* anchor;
543  TrayBubbleView::AnchorType anchor_type;
544  // Tray items might want to show notifications while we are creating and
545  // initializing the |system_bubble_| - but it might not be fully initialized
546  // when coming here - this would produce a crashed like crbug.com/247416.
547  // As such we check the existence of the widget here.
548  if (system_bubble_.get() &&
549      system_bubble_->bubble_view() &&
550      system_bubble_->bubble_view()->GetWidget()) {
551    anchor = system_bubble_->bubble_view();
552    anchor_type = TrayBubbleView::ANCHOR_TYPE_BUBBLE;
553  } else {
554    anchor = tray_container();
555    anchor_type = TrayBubbleView::ANCHOR_TYPE_TRAY;
556  }
557  TrayBubbleView::InitParams init_params(anchor_type,
558                                         GetAnchorAlignment(),
559                                         kTrayPopupMinWidth,
560                                         kTrayPopupMaxWidth);
561  init_params.first_item_has_no_margin = true;
562  init_params.arrow_color = kBackgroundColor;
563  init_params.arrow_offset = GetTrayXOffset(notification_items_[0]);
564  notification_bubble_.reset(new SystemBubbleWrapper(notification_bubble));
565  notification_bubble_->InitView(this, anchor, &init_params, false);
566
567  if (notification_bubble->bubble_view()->child_count() == 0) {
568    // It is possible that none of the items generated actual notifications.
569    DestroyNotificationBubble();
570    return;
571  }
572  if (hide_notifications_)
573    notification_bubble->SetVisible(false);
574  else
575    UpdateWebNotifications();
576}
577
578void SystemTray::UpdateWebNotifications() {
579  TrayBubbleView* bubble_view = NULL;
580  if (notification_bubble_)
581    bubble_view = notification_bubble_->bubble_view();
582  else if (system_bubble_)
583    bubble_view = system_bubble_->bubble_view();
584
585  int height = 0;
586  if (bubble_view) {
587    gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
588        bubble_view->GetWidget()->GetNativeView()).work_area();
589    if (GetShelfLayoutManager()->GetAlignment() != SHELF_ALIGNMENT_TOP) {
590      height = std::max(
591          0, work_area.height() - bubble_view->GetBoundsInScreen().y());
592    } else {
593      height = std::max(
594          0, bubble_view->GetBoundsInScreen().bottom() - work_area.y());
595    }
596  }
597  status_area_widget()->web_notification_tray()->SetSystemTrayHeight(height);
598}
599
600base::string16 SystemTray::GetAccessibleTimeString(
601    const base::Time& now) const {
602  base::HourClockType hour_type =
603      ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
604  return base::TimeFormatTimeOfDayWithHourClockType(
605      now, hour_type, base::kKeepAmPm);
606}
607
608void SystemTray::SetShelfAlignment(ShelfAlignment alignment) {
609  if (alignment == shelf_alignment())
610    return;
611  TrayBackgroundView::SetShelfAlignment(alignment);
612  UpdateAfterShelfAlignmentChange(alignment);
613  // Destroy any existing bubble so that it is rebuilt correctly.
614  CloseSystemBubbleAndDeactivateSystemTray();
615  // Rebuild any notification bubble.
616  if (notification_bubble_) {
617    notification_bubble_.reset();
618    UpdateNotificationBubble();
619  }
620}
621
622void SystemTray::AnchorUpdated() {
623  if (notification_bubble_) {
624    notification_bubble_->bubble_view()->UpdateBubble();
625    // Ensure that the notification buble is above the shelf/status area.
626    notification_bubble_->bubble_view()->GetWidget()->StackAtTop();
627    UpdateBubbleViewArrow(notification_bubble_->bubble_view());
628  }
629  if (system_bubble_) {
630    system_bubble_->bubble_view()->UpdateBubble();
631    UpdateBubbleViewArrow(system_bubble_->bubble_view());
632  }
633}
634
635void SystemTray::BubbleResized(const TrayBubbleView* bubble_view) {
636  UpdateWebNotifications();
637}
638
639void SystemTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {
640  if (system_bubble_.get() && bubble_view == system_bubble_->bubble_view()) {
641    DestroySystemBubble();
642    UpdateNotificationBubble();  // State changed, re-create notifications.
643    GetShelfLayoutManager()->UpdateAutoHideState();
644  } else if (notification_bubble_.get() &&
645             bubble_view == notification_bubble_->bubble_view()) {
646    DestroyNotificationBubble();
647  }
648}
649
650bool SystemTray::ClickedOutsideBubble() {
651  if (!system_bubble_ || system_bubble_->is_persistent())
652    return false;
653  HideBubbleWithView(system_bubble_->bubble_view());
654  return true;
655}
656
657void SystemTray::BubbleViewDestroyed() {
658  if (system_bubble_) {
659    system_bubble_->bubble()->DestroyItemViews();
660    system_bubble_->bubble()->BubbleViewDestroyed();
661  }
662}
663
664void SystemTray::OnMouseEnteredView() {
665  if (system_bubble_)
666    system_bubble_->bubble()->StopAutoCloseTimer();
667}
668
669void SystemTray::OnMouseExitedView() {
670  if (system_bubble_)
671    system_bubble_->bubble()->RestartAutoCloseTimer();
672}
673
674base::string16 SystemTray::GetAccessibleNameForBubble() {
675  return GetAccessibleNameForTray();
676}
677
678gfx::Rect SystemTray::GetAnchorRect(
679    views::Widget* anchor_widget,
680    TrayBubbleView::AnchorType anchor_type,
681    TrayBubbleView::AnchorAlignment anchor_alignment) const {
682  return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
683}
684
685void SystemTray::HideBubble(const TrayBubbleView* bubble_view) {
686  HideBubbleWithView(bubble_view);
687}
688
689views::View* SystemTray::GetTrayItemViewForTest(SystemTrayItem* item) {
690  std::map<SystemTrayItem*, views::View*>::iterator it =
691      tray_item_map_.find(item);
692  return it == tray_item_map_.end() ? NULL : it->second;
693}
694
695TrayDate* SystemTray::GetTrayDateForTesting() const { return tray_date_; }
696
697bool SystemTray::PerformAction(const ui::Event& event) {
698  // If we're already showing the default view, hide it; otherwise, show it
699  // (and hide any popup that's currently shown).
700  if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) {
701    system_bubble_->bubble()->Close();
702  } else {
703    int arrow_offset = TrayBubbleView::InitParams::kArrowDefaultOffset;
704    if (event.IsMouseEvent() || event.type() == ui::ET_GESTURE_TAP) {
705      const ui::LocatedEvent& located_event =
706          static_cast<const ui::LocatedEvent&>(event);
707      if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM ||
708          shelf_alignment() == SHELF_ALIGNMENT_TOP) {
709        gfx::Point point(located_event.x(), 0);
710        ConvertPointToWidget(this, &point);
711        arrow_offset = point.x();
712      }
713    }
714    ShowDefaultViewWithOffset(BUBBLE_CREATE_NEW, arrow_offset, false);
715  }
716  return true;
717}
718
719void SystemTray::CloseSystemBubbleAndDeactivateSystemTray() {
720  system_bubble_.reset();
721  // When closing a system bubble with the alternate shelf layout, we need to
722  // turn off the active tinting of the shelf.
723  if (full_system_tray_menu_) {
724    SetDrawBackgroundAsActive(false);
725    full_system_tray_menu_ = false;
726  }
727}
728
729}  // namespace ash
730