Home | History | Annotate | Download | only in web_notification
      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/web_notification/web_notification_tray.h"
      6 
      7 #include "ash/ash_switches.h"
      8 #include "ash/root_window_controller.h"
      9 #include "ash/shelf/shelf_layout_manager.h"
     10 #include "ash/shelf/shelf_layout_manager_observer.h"
     11 #include "ash/shelf/shelf_widget.h"
     12 #include "ash/shell.h"
     13 #include "ash/shell_window_ids.h"
     14 #include "ash/system/status_area_widget.h"
     15 #include "ash/system/tray/system_tray.h"
     16 #include "ash/system/tray/tray_background_view.h"
     17 #include "ash/system/tray/tray_bubble_wrapper.h"
     18 #include "ash/system/tray/tray_constants.h"
     19 #include "ash/system/tray/tray_utils.h"
     20 #include "base/auto_reset.h"
     21 #include "base/i18n/number_formatting.h"
     22 #include "base/i18n/rtl.h"
     23 #include "base/strings/utf_string_conversions.h"
     24 #include "grit/ash_strings.h"
     25 #include "grit/ui_strings.h"
     26 #include "ui/aura/window.h"
     27 #include "ui/aura/window_event_dispatcher.h"
     28 #include "ui/base/l10n/l10n_util.h"
     29 #include "ui/gfx/screen.h"
     30 #include "ui/message_center/message_center_style.h"
     31 #include "ui/message_center/message_center_tray_delegate.h"
     32 #include "ui/message_center/views/message_bubble_base.h"
     33 #include "ui/message_center/views/message_center_bubble.h"
     34 #include "ui/message_center/views/message_popup_collection.h"
     35 #include "ui/views/bubble/tray_bubble_view.h"
     36 #include "ui/views/controls/button/custom_button.h"
     37 #include "ui/views/controls/image_view.h"
     38 #include "ui/views/controls/label.h"
     39 #include "ui/views/controls/menu/menu_runner.h"
     40 #include "ui/views/layout/fill_layout.h"
     41 
     42 #if defined(OS_CHROMEOS)
     43 
     44 namespace message_center {
     45 
     46 MessageCenterTrayDelegate* CreateMessageCenterTray() {
     47   // On Windows+Ash the Tray will not be hosted in ash::Shell.
     48   NOTREACHED();
     49   return NULL;
     50 }
     51 
     52 }  // namespace message_center
     53 
     54 #endif  // defined(OS_CHROMEOS)
     55 
     56 namespace ash {
     57 namespace {
     58 
     59 // Menu commands
     60 const int kToggleQuietMode = 0;
     61 const int kEnableQuietModeDay = 2;
     62 
     63 }
     64 
     65 namespace {
     66 
     67 const SkColor kWebNotificationColorNoUnread =
     68     SkColorSetARGB(128, 255, 255, 255);
     69 const SkColor kWebNotificationColorWithUnread = SK_ColorWHITE;
     70 
     71 }
     72 
     73 // Observes the change of work area (including temporary change by auto-hide)
     74 // and notifies MessagePopupCollection.
     75 class WorkAreaObserver : public ShelfLayoutManagerObserver,
     76                          public ShellObserver {
     77  public:
     78   WorkAreaObserver();
     79   virtual ~WorkAreaObserver();
     80 
     81   void SetSystemTrayHeight(int height);
     82 
     83   // Starts observing |shelf| and shell and sends the change to |collection|.
     84   void StartObserving(message_center::MessagePopupCollection* collection,
     85                       aura::Window* root_window);
     86 
     87   // Stops the observing session.
     88   void StopObserving();
     89 
     90   // Overridden from ShellObserver:
     91   virtual void OnDisplayWorkAreaInsetsChanged() OVERRIDE;
     92 
     93   // Overridden from ShelfLayoutManagerObserver:
     94   virtual void OnAutoHideStateChanged(ShelfAutoHideState new_state) OVERRIDE;
     95 
     96  private:
     97   // Updates |shelf_| from |root_window_|.
     98   void UpdateShelf();
     99 
    100   message_center::MessagePopupCollection* collection_;
    101   aura::Window* root_window_;
    102   ShelfLayoutManager* shelf_;
    103   int system_tray_height_;
    104 
    105   DISALLOW_COPY_AND_ASSIGN(WorkAreaObserver);
    106 };
    107 
    108 WorkAreaObserver::WorkAreaObserver()
    109     : collection_(NULL),
    110       root_window_(NULL),
    111       shelf_(NULL),
    112       system_tray_height_(0) {
    113 }
    114 
    115 WorkAreaObserver::~WorkAreaObserver() {
    116   StopObserving();
    117 }
    118 
    119 void WorkAreaObserver::SetSystemTrayHeight(int height) {
    120   system_tray_height_ = height;
    121 
    122   // If the shelf is shown during auto-hide state, the distance from the edge
    123   // should be reduced by the height of shelf's shown height.
    124   if (shelf_ && shelf_->visibility_state() == SHELF_AUTO_HIDE &&
    125       shelf_->auto_hide_state() == SHELF_AUTO_HIDE_SHOWN) {
    126     system_tray_height_ -= kShelfSize - ShelfLayoutManager::kAutoHideSize;
    127   }
    128 
    129   if (system_tray_height_ > 0)
    130     system_tray_height_ += message_center::kMarginBetweenItems;
    131 
    132   if (!shelf_)
    133     return;
    134 
    135   OnAutoHideStateChanged(shelf_->auto_hide_state());
    136 }
    137 
    138 void WorkAreaObserver::StartObserving(
    139     message_center::MessagePopupCollection* collection,
    140     aura::Window* root_window) {
    141   DCHECK(collection);
    142   collection_ = collection;
    143   root_window_ = root_window;
    144   UpdateShelf();
    145   Shell::GetInstance()->AddShellObserver(this);
    146   if (system_tray_height_ > 0)
    147     OnAutoHideStateChanged(shelf_->auto_hide_state());
    148 }
    149 
    150 void WorkAreaObserver::StopObserving() {
    151   Shell::GetInstance()->RemoveShellObserver(this);
    152   if (shelf_)
    153     shelf_->RemoveObserver(this);
    154   collection_ = NULL;
    155   shelf_ = NULL;
    156 }
    157 
    158 void WorkAreaObserver::OnDisplayWorkAreaInsetsChanged() {
    159   UpdateShelf();
    160 
    161   collection_->OnDisplayMetricsChanged(
    162       Shell::GetScreen()->GetDisplayNearestWindow(
    163           shelf_->shelf_widget()->GetNativeView()),
    164       gfx::DisplayObserver::DISPLAY_METRIC_WORK_AREA);
    165 }
    166 
    167 void WorkAreaObserver::OnAutoHideStateChanged(ShelfAutoHideState new_state) {
    168   gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
    169       shelf_->shelf_widget()->GetNativeView());
    170   gfx::Rect work_area = display.work_area();
    171   int width = 0;
    172   if ((shelf_->visibility_state() == SHELF_AUTO_HIDE) &&
    173       new_state == SHELF_AUTO_HIDE_SHOWN) {
    174     // Since the work_area is already reduced by kAutoHideSize, the inset width
    175     // should be just the difference.
    176     width = kShelfSize - ShelfLayoutManager::kAutoHideSize;
    177   }
    178   work_area.Inset(shelf_->SelectValueForShelfAlignment(
    179       gfx::Insets(0, 0, width, 0),
    180       gfx::Insets(0, width, 0, 0),
    181       gfx::Insets(0, 0, 0, width),
    182       gfx::Insets(width, 0, 0, 0)));
    183   if (system_tray_height_ > 0) {
    184     work_area.set_height(
    185         std::max(0, work_area.height() - system_tray_height_));
    186     if (shelf_->GetAlignment() == SHELF_ALIGNMENT_TOP)
    187       work_area.set_y(work_area.y() + system_tray_height_);
    188   }
    189   collection_->SetDisplayInfo(work_area, display.bounds());
    190 }
    191 
    192 void WorkAreaObserver::UpdateShelf() {
    193   if (shelf_)
    194     return;
    195 
    196   shelf_ = ShelfLayoutManager::ForShelf(root_window_);
    197   if (shelf_)
    198     shelf_->AddObserver(this);
    199 }
    200 
    201 // Class to initialize and manage the WebNotificationBubble and
    202 // TrayBubbleWrapper instances for a bubble.
    203 class WebNotificationBubbleWrapper {
    204  public:
    205   // Takes ownership of |bubble| and creates |bubble_wrapper_|.
    206   WebNotificationBubbleWrapper(WebNotificationTray* tray,
    207                                message_center::MessageBubbleBase* bubble) {
    208     bubble_.reset(bubble);
    209     views::TrayBubbleView::AnchorAlignment anchor_alignment =
    210         tray->GetAnchorAlignment();
    211     views::TrayBubbleView::InitParams init_params =
    212         bubble->GetInitParams(anchor_alignment);
    213     views::View* anchor = tray->tray_container();
    214     if (anchor_alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM) {
    215       gfx::Point bounds(anchor->width() / 2, 0);
    216       views::View::ConvertPointToWidget(anchor, &bounds);
    217       init_params.arrow_offset = bounds.x();
    218     }
    219     views::TrayBubbleView* bubble_view = views::TrayBubbleView::Create(
    220         tray->GetBubbleWindowContainer(), anchor, tray, &init_params);
    221     bubble_view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
    222     bubble_wrapper_.reset(new TrayBubbleWrapper(tray, bubble_view));
    223     bubble->InitializeContents(bubble_view);
    224   }
    225 
    226   message_center::MessageBubbleBase* bubble() const { return bubble_.get(); }
    227 
    228   // Convenience accessors.
    229   views::TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }
    230 
    231  private:
    232   scoped_ptr<message_center::MessageBubbleBase> bubble_;
    233   scoped_ptr<TrayBubbleWrapper> bubble_wrapper_;
    234 
    235   DISALLOW_COPY_AND_ASSIGN(WebNotificationBubbleWrapper);
    236 };
    237 
    238 class WebNotificationButton : public views::CustomButton {
    239  public:
    240   WebNotificationButton(views::ButtonListener* listener)
    241       : views::CustomButton(listener),
    242         is_bubble_visible_(false),
    243         unread_count_(0) {
    244     SetLayoutManager(new views::FillLayout);
    245     unread_label_ = new views::Label();
    246     SetupLabelForTray(unread_label_);
    247     AddChildView(unread_label_);
    248   }
    249 
    250   void SetBubbleVisible(bool visible) {
    251     if (visible == is_bubble_visible_)
    252       return;
    253 
    254     is_bubble_visible_ = visible;
    255     UpdateIconVisibility();
    256   }
    257 
    258   void SetUnreadCount(int unread_count) {
    259     // base::FormatNumber doesn't convert to arabic numeric characters.
    260     // TODO(mukai): use ICU to support conversion for such locales.
    261     unread_count_ = unread_count;
    262     // TODO(mukai): move NINE_PLUS message to ui_strings, it doesn't need to be
    263     // in ash_strings.
    264     unread_label_->SetText((unread_count > 9) ?
    265         l10n_util::GetStringUTF16(IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS) :
    266         base::FormatNumber(unread_count));
    267     UpdateIconVisibility();
    268   }
    269 
    270  protected:
    271   // Overridden from views::ImageButton:
    272   virtual gfx::Size GetPreferredSize() const OVERRIDE {
    273     return gfx::Size(kShelfItemHeight, kShelfItemHeight);
    274   }
    275 
    276   virtual int GetHeightForWidth(int width) const OVERRIDE {
    277     return GetPreferredSize().height();
    278   }
    279 
    280  private:
    281   void UpdateIconVisibility() {
    282     unread_label_->SetEnabledColor(
    283         (!is_bubble_visible_ && unread_count_ > 0) ?
    284         kWebNotificationColorWithUnread : kWebNotificationColorNoUnread);
    285     SchedulePaint();
    286   }
    287 
    288   bool is_bubble_visible_;
    289   int unread_count_;
    290 
    291   views::Label* unread_label_;
    292 
    293   DISALLOW_COPY_AND_ASSIGN(WebNotificationButton);
    294 };
    295 
    296 WebNotificationTray::WebNotificationTray(StatusAreaWidget* status_area_widget)
    297     : TrayBackgroundView(status_area_widget),
    298       button_(NULL),
    299       show_message_center_on_unlock_(false),
    300       should_update_tray_content_(false),
    301       should_block_shelf_auto_hide_(false) {
    302   button_ = new WebNotificationButton(this);
    303   button_->set_triggerable_event_flags(
    304       ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON);
    305   tray_container()->AddChildView(button_);
    306   SetContentsBackground();
    307   tray_container()->SetBorder(views::Border::NullBorder());
    308   message_center_tray_.reset(new message_center::MessageCenterTray(
    309       this,
    310       message_center::MessageCenter::Get()));
    311   popup_collection_.reset(new message_center::MessagePopupCollection(
    312       ash::Shell::GetContainer(
    313           status_area_widget->GetNativeView()->GetRootWindow(),
    314           kShellWindowId_StatusContainer),
    315       message_center(),
    316       message_center_tray_.get(),
    317       true));
    318   work_area_observer_.reset(new WorkAreaObserver());
    319   work_area_observer_->StartObserving(
    320       popup_collection_.get(),
    321       status_area_widget->GetNativeView()->GetRootWindow());
    322   OnMessageCenterTrayChanged();
    323 }
    324 
    325 WebNotificationTray::~WebNotificationTray() {
    326   // Release any child views that might have back pointers before ~View().
    327   message_center_bubble_.reset();
    328   popup_collection_.reset();
    329   work_area_observer_.reset();
    330 }
    331 
    332 // Public methods.
    333 
    334 bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) {
    335   if (!ShouldShowMessageCenter())
    336     return false;
    337 
    338   should_block_shelf_auto_hide_ = true;
    339   message_center::MessageCenterBubble* message_center_bubble =
    340       new message_center::MessageCenterBubble(
    341           message_center(),
    342           message_center_tray_.get(),
    343           true);
    344 
    345   int max_height = 0;
    346   aura::Window* status_area_window = status_area_widget()->GetNativeView();
    347   switch (GetShelfLayoutManager()->GetAlignment()) {
    348     case SHELF_ALIGNMENT_BOTTOM: {
    349       gfx::Rect shelf_bounds = GetShelfLayoutManager()->GetIdealBounds();
    350       max_height = shelf_bounds.y();
    351       break;
    352     }
    353     case SHELF_ALIGNMENT_TOP: {
    354       aura::Window* root = status_area_window->GetRootWindow();
    355       max_height =
    356           root->bounds().height() - status_area_window->bounds().height();
    357       break;
    358     }
    359     case SHELF_ALIGNMENT_LEFT:
    360     case SHELF_ALIGNMENT_RIGHT: {
    361       // Assume that the bottom line of the status area widget and the bubble
    362       // are aligned.
    363       max_height = status_area_window->GetBoundsInRootWindow().bottom();
    364       break;
    365     }
    366     default:
    367       NOTREACHED();
    368   }
    369 
    370   message_center_bubble->SetMaxHeight(std::max(0,
    371                                                max_height - kTraySpacing));
    372   if (show_settings)
    373     message_center_bubble->SetSettingsVisible();
    374   message_center_bubble_.reset(
    375       new WebNotificationBubbleWrapper(this, message_center_bubble));
    376 
    377   status_area_widget()->SetHideSystemNotifications(true);
    378   GetShelfLayoutManager()->UpdateAutoHideState();
    379   button_->SetBubbleVisible(true);
    380   SetDrawBackgroundAsActive(true);
    381   return true;
    382 }
    383 
    384 bool WebNotificationTray::ShowMessageCenter() {
    385   return ShowMessageCenterInternal(false /* show_settings */);
    386 }
    387 
    388 void WebNotificationTray::HideMessageCenter() {
    389   if (!message_center_bubble())
    390     return;
    391   SetDrawBackgroundAsActive(false);
    392   message_center_bubble_.reset();
    393   should_block_shelf_auto_hide_ = false;
    394   show_message_center_on_unlock_ = false;
    395   status_area_widget()->SetHideSystemNotifications(false);
    396   GetShelfLayoutManager()->UpdateAutoHideState();
    397   button_->SetBubbleVisible(false);
    398 }
    399 
    400 void WebNotificationTray::SetSystemTrayHeight(int height) {
    401   work_area_observer_->SetSystemTrayHeight(height);
    402 }
    403 
    404 bool WebNotificationTray::ShowPopups() {
    405   if (message_center_bubble())
    406     return false;
    407 
    408   popup_collection_->DoUpdateIfPossible();
    409   return true;
    410 }
    411 
    412 void WebNotificationTray::HidePopups() {
    413   DCHECK(popup_collection_.get());
    414   popup_collection_->MarkAllPopupsShown();
    415 }
    416 
    417 // Private methods.
    418 
    419 bool WebNotificationTray::ShouldShowMessageCenter() {
    420   return status_area_widget()->login_status() != user::LOGGED_IN_LOCKED &&
    421       !(status_area_widget()->system_tray() &&
    422         status_area_widget()->system_tray()->HasNotificationBubble());
    423 }
    424 
    425 bool WebNotificationTray::ShouldBlockShelfAutoHide() const {
    426   return should_block_shelf_auto_hide_;
    427 }
    428 
    429 bool WebNotificationTray::IsMessageCenterBubbleVisible() const {
    430   return (message_center_bubble() &&
    431           message_center_bubble()->bubble()->IsVisible());
    432 }
    433 
    434 bool WebNotificationTray::IsMouseInNotificationBubble() const {
    435   return false;
    436 }
    437 
    438 void WebNotificationTray::ShowMessageCenterBubble() {
    439   if (!IsMessageCenterBubbleVisible())
    440     message_center_tray_->ShowMessageCenterBubble();
    441 }
    442 
    443 void WebNotificationTray::UpdateAfterLoginStatusChange(
    444     user::LoginStatus login_status) {
    445   OnMessageCenterTrayChanged();
    446 }
    447 
    448 void WebNotificationTray::SetShelfAlignment(ShelfAlignment alignment) {
    449   if (alignment == shelf_alignment())
    450     return;
    451   TrayBackgroundView::SetShelfAlignment(alignment);
    452   tray_container()->SetBorder(views::Border::NullBorder());
    453   // Destroy any existing bubble so that it will be rebuilt correctly.
    454   message_center_tray_->HideMessageCenterBubble();
    455   message_center_tray_->HidePopupBubble();
    456 }
    457 
    458 void WebNotificationTray::AnchorUpdated() {
    459   if (message_center_bubble()) {
    460     message_center_bubble()->bubble_view()->UpdateBubble();
    461     UpdateBubbleViewArrow(message_center_bubble()->bubble_view());
    462   }
    463 }
    464 
    465 base::string16 WebNotificationTray::GetAccessibleNameForTray() {
    466   return l10n_util::GetStringUTF16(
    467       IDS_MESSAGE_CENTER_ACCESSIBLE_NAME);
    468 }
    469 
    470 void WebNotificationTray::HideBubbleWithView(
    471     const views::TrayBubbleView* bubble_view) {
    472   if (message_center_bubble() &&
    473       bubble_view == message_center_bubble()->bubble_view()) {
    474     message_center_tray_->HideMessageCenterBubble();
    475   } else if (popup_collection_.get()) {
    476     message_center_tray_->HidePopupBubble();
    477   }
    478 }
    479 
    480 bool WebNotificationTray::PerformAction(const ui::Event& event) {
    481   if (message_center_bubble())
    482     message_center_tray_->HideMessageCenterBubble();
    483   else
    484     message_center_tray_->ShowMessageCenterBubble();
    485   return true;
    486 }
    487 
    488 void WebNotificationTray::BubbleViewDestroyed() {
    489   if (message_center_bubble())
    490     message_center_bubble()->bubble()->BubbleViewDestroyed();
    491 }
    492 
    493 void WebNotificationTray::OnMouseEnteredView() {}
    494 
    495 void WebNotificationTray::OnMouseExitedView() {}
    496 
    497 base::string16 WebNotificationTray::GetAccessibleNameForBubble() {
    498   return GetAccessibleNameForTray();
    499 }
    500 
    501 gfx::Rect WebNotificationTray::GetAnchorRect(
    502     views::Widget* anchor_widget,
    503     views::TrayBubbleView::AnchorType anchor_type,
    504     views::TrayBubbleView::AnchorAlignment anchor_alignment) const {
    505   return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
    506 }
    507 
    508 void WebNotificationTray::HideBubble(const views::TrayBubbleView* bubble_view) {
    509   HideBubbleWithView(bubble_view);
    510 }
    511 
    512 bool WebNotificationTray::ShowNotifierSettings() {
    513   if (message_center_bubble()) {
    514     static_cast<message_center::MessageCenterBubble*>(
    515         message_center_bubble()->bubble())->SetSettingsVisible();
    516     return true;
    517   }
    518   return ShowMessageCenterInternal(true /* show_settings */);
    519 }
    520 
    521 bool WebNotificationTray::IsContextMenuEnabled() const {
    522   user::LoginStatus login_status = status_area_widget()->login_status();
    523   return login_status != user::LOGGED_IN_NONE
    524       && login_status != user::LOGGED_IN_LOCKED;
    525 }
    526 
    527 message_center::MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
    528   return message_center_tray_.get();
    529 }
    530 
    531 bool WebNotificationTray::IsCommandIdChecked(int command_id) const {
    532   if (command_id != kToggleQuietMode)
    533     return false;
    534   return message_center()->IsQuietMode();
    535 }
    536 
    537 bool WebNotificationTray::IsCommandIdEnabled(int command_id) const {
    538   return true;
    539 }
    540 
    541 bool WebNotificationTray::GetAcceleratorForCommandId(
    542     int command_id,
    543     ui::Accelerator* accelerator) {
    544   return false;
    545 }
    546 
    547 void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) {
    548   if (command_id == kToggleQuietMode) {
    549     bool in_quiet_mode = message_center()->IsQuietMode();
    550     message_center()->SetQuietMode(!in_quiet_mode);
    551     return;
    552   }
    553   base::TimeDelta expires_in = command_id == kEnableQuietModeDay ?
    554       base::TimeDelta::FromDays(1):
    555       base::TimeDelta::FromHours(1);
    556   message_center()->EnterQuietModeWithExpire(expires_in);
    557 }
    558 
    559 void WebNotificationTray::ButtonPressed(views::Button* sender,
    560                                         const ui::Event& event) {
    561   DCHECK_EQ(button_, sender);
    562   PerformAction(event);
    563 }
    564 
    565 void WebNotificationTray::OnMessageCenterTrayChanged() {
    566   // Do not update the tray contents directly. Multiple change events can happen
    567   // consecutively, and calling Update in the middle of those events will show
    568   // intermediate unread counts for a moment.
    569   should_update_tray_content_ = true;
    570   base::MessageLoop::current()->PostTask(
    571       FROM_HERE,
    572       base::Bind(&WebNotificationTray::UpdateTrayContent, AsWeakPtr()));
    573 }
    574 
    575 void WebNotificationTray::UpdateTrayContent() {
    576   if (!should_update_tray_content_)
    577     return;
    578   should_update_tray_content_ = false;
    579 
    580   message_center::MessageCenter* message_center =
    581       message_center_tray_->message_center();
    582   button_->SetUnreadCount(message_center->UnreadNotificationCount());
    583   if (IsMessageCenterBubbleVisible())
    584     button_->SetState(views::CustomButton::STATE_PRESSED);
    585   else
    586     button_->SetState(views::CustomButton::STATE_NORMAL);
    587   SetVisible((status_area_widget()->login_status() != user::LOGGED_IN_NONE) &&
    588              (status_area_widget()->login_status() != user::LOGGED_IN_LOCKED) &&
    589              (message_center->NotificationCount() > 0));
    590   Layout();
    591   SchedulePaint();
    592 }
    593 
    594 bool WebNotificationTray::ClickedOutsideBubble() {
    595   // Only hide the message center
    596   if (!message_center_bubble())
    597     return false;
    598 
    599   message_center_tray_->HideMessageCenterBubble();
    600   return true;
    601 }
    602 
    603 message_center::MessageCenter* WebNotificationTray::message_center() const {
    604   return message_center_tray_->message_center();
    605 }
    606 
    607 // Methods for testing
    608 
    609 bool WebNotificationTray::IsPopupVisible() const {
    610   return message_center_tray_->popups_visible();
    611 }
    612 
    613 message_center::MessageCenterBubble*
    614 WebNotificationTray::GetMessageCenterBubbleForTest() {
    615   if (!message_center_bubble())
    616     return NULL;
    617   return static_cast<message_center::MessageCenterBubble*>(
    618       message_center_bubble()->bubble());
    619 }
    620 
    621 }  // namespace ash
    622