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