Home | History | Annotate | Download | only in tray
      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 
     69 using views::TrayBubbleView;
     70 
     71 namespace ash {
     72 
     73 // The minimum width of the system tray menu width.
     74 const int kMinimumSystemTrayMenuWidth = 300;
     75 
     76 // Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper
     77 // instances for a bubble.
     78 
     79 class 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 
    126 SystemTray::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 
    137 SystemTray::~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 
    148 void SystemTray::InitializeTrayItems(SystemTrayDelegate* delegate) {
    149   TrayBackgroundView::Initialize();
    150   CreateItems(delegate);
    151 }
    152 
    153 void 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 
    215 void 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 
    229 void SystemTray::RemoveTrayItem(SystemTrayItem* item) {
    230   NOTIMPLEMENTED();
    231 }
    232 
    233 const std::vector<SystemTrayItem*>& SystemTray::GetTrayItems() const {
    234   return items_.get();
    235 }
    236 
    237 void SystemTray::ShowDefaultView(BubbleCreationType creation_type) {
    238   ShowDefaultViewWithOffset(
    239       creation_type,
    240       TrayBubbleView::InitParams::kArrowDefaultOffset,
    241       false);
    242 }
    243 
    244 void SystemTray::ShowPersistentDefaultView() {
    245   ShowItems(items_.get(),
    246             false,
    247             false,
    248             BUBBLE_CREATE_NEW,
    249             TrayBubbleView::InitParams::kArrowDefaultOffset,
    250             true);
    251 }
    252 
    253 void 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 
    270 void SystemTray::SetDetailedViewCloseDelay(int close_delay) {
    271   if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DETAILED))
    272     system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
    273 }
    274 
    275 void SystemTray::HideDetailedView(SystemTrayItem* item) {
    276   if (item != detailed_item_)
    277     return;
    278   DestroySystemBubble();
    279   UpdateNotificationBubble();
    280 }
    281 
    282 void 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 
    290 void 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 
    301 void 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 
    320 void 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 
    328 void SystemTray::SetHideNotifications(bool hide_notifications) {
    329   if (notification_bubble_)
    330     notification_bubble_->bubble()->SetVisible(!hide_notifications);
    331   hide_notifications_ = hide_notifications;
    332 }
    333 
    334 bool SystemTray::ShouldShowShelf() const {
    335   return system_bubble_.get() && system_bubble_->bubble()->ShouldShowShelf();
    336 }
    337 
    338 bool SystemTray::HasSystemBubble() const {
    339   return system_bubble_.get() != NULL;
    340 }
    341 
    342 bool SystemTray::HasNotificationBubble() const {
    343   return notification_bubble_.get() != NULL;
    344 }
    345 
    346 SystemTrayBubble* SystemTray::GetSystemBubble() {
    347   if (!system_bubble_)
    348     return NULL;
    349   return system_bubble_->bubble();
    350 }
    351 
    352 bool SystemTray::IsAnyBubbleVisible() const {
    353   return ((system_bubble_.get() &&
    354            system_bubble_->bubble()->IsVisible()) ||
    355           (notification_bubble_.get() &&
    356            notification_bubble_->bubble()->IsVisible()));
    357 }
    358 
    359 bool SystemTray::IsMouseInNotificationBubble() const {
    360   if (!notification_bubble_)
    361     return false;
    362   return notification_bubble_->bubble_view()->GetBoundsInScreen().Contains(
    363       Shell::GetScreen()->GetCursorScreenPoint());
    364 }
    365 
    366 bool SystemTray::CloseSystemBubble() const {
    367   if (!system_bubble_)
    368     return false;
    369   system_bubble_->bubble()->Close();
    370   return true;
    371 }
    372 
    373 views::View* SystemTray::GetHelpButtonView() const {
    374   return tray_date_->GetHelpButtonView();
    375 }
    376 
    377 bool SystemTray::CloseNotificationBubbleForTest() const {
    378   if (!notification_bubble_)
    379     return false;
    380   notification_bubble_->bubble()->Close();
    381   return true;
    382 }
    383 
    384 // Private methods.
    385 
    386 bool SystemTray::HasSystemBubbleType(SystemTrayBubble::BubbleType type) {
    387   DCHECK(type != SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
    388   return system_bubble_.get() && system_bubble_->bubble_type() == type;
    389 }
    390 
    391 void SystemTray::DestroySystemBubble() {
    392   CloseSystemBubbleAndDeactivateSystemTray();
    393   detailed_item_ = NULL;
    394   UpdateWebNotifications();
    395 }
    396 
    397 void SystemTray::DestroyNotificationBubble() {
    398   if (notification_bubble_) {
    399     notification_bubble_.reset();
    400     UpdateWebNotifications();
    401   }
    402 }
    403 
    404 base::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 
    414 int 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 
    437 void 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 
    447 void 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 
    531 void 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 
    578 void 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 
    600 base::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 
    608 void 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 
    622 void 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 
    635 void SystemTray::BubbleResized(const TrayBubbleView* bubble_view) {
    636   UpdateWebNotifications();
    637 }
    638 
    639 void 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 
    650 bool SystemTray::ClickedOutsideBubble() {
    651   if (!system_bubble_ || system_bubble_->is_persistent())
    652     return false;
    653   HideBubbleWithView(system_bubble_->bubble_view());
    654   return true;
    655 }
    656 
    657 void SystemTray::BubbleViewDestroyed() {
    658   if (system_bubble_) {
    659     system_bubble_->bubble()->DestroyItemViews();
    660     system_bubble_->bubble()->BubbleViewDestroyed();
    661   }
    662 }
    663 
    664 void SystemTray::OnMouseEnteredView() {
    665   if (system_bubble_)
    666     system_bubble_->bubble()->StopAutoCloseTimer();
    667 }
    668 
    669 void SystemTray::OnMouseExitedView() {
    670   if (system_bubble_)
    671     system_bubble_->bubble()->RestartAutoCloseTimer();
    672 }
    673 
    674 base::string16 SystemTray::GetAccessibleNameForBubble() {
    675   return GetAccessibleNameForTray();
    676 }
    677 
    678 gfx::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 
    685 void SystemTray::HideBubble(const TrayBubbleView* bubble_view) {
    686   HideBubbleWithView(bubble_view);
    687 }
    688 
    689 views::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 
    695 TrayDate* SystemTray::GetTrayDateForTesting() const { return tray_date_; }
    696 
    697 bool 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 
    719 void 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