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 "ash/system/web_notification/ash_popup_alignment_delegate.h"
     21 #include "base/auto_reset.h"
     22 #include "base/i18n/number_formatting.h"
     23 #include "base/i18n/rtl.h"
     24 #include "base/strings/utf_string_conversions.h"
     25 #include "grit/ash_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/strings/grit/ui_strings.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 {
     67 
     68 const SkColor kWebNotificationColorNoUnread =
     69     SkColorSetARGB(128, 255, 255, 255);
     70 const SkColor kWebNotificationColorWithUnread = SK_ColorWHITE;
     71 
     72 }
     73 
     74 // Class to initialize and manage the WebNotificationBubble and
     75 // TrayBubbleWrapper instances for a bubble.
     76 class WebNotificationBubbleWrapper {
     77  public:
     78   // Takes ownership of |bubble| and creates |bubble_wrapper_|.
     79   WebNotificationBubbleWrapper(WebNotificationTray* tray,
     80                                message_center::MessageBubbleBase* bubble) {
     81     bubble_.reset(bubble);
     82     views::TrayBubbleView::AnchorAlignment anchor_alignment =
     83         tray->GetAnchorAlignment();
     84     views::TrayBubbleView::InitParams init_params =
     85         bubble->GetInitParams(anchor_alignment);
     86     views::View* anchor = tray->tray_container();
     87     if (anchor_alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM) {
     88       gfx::Point bounds(anchor->width() / 2, 0);
     89       views::View::ConvertPointToWidget(anchor, &bounds);
     90       init_params.arrow_offset = bounds.x();
     91     }
     92     views::TrayBubbleView* bubble_view = views::TrayBubbleView::Create(
     93         tray->GetBubbleWindowContainer(), anchor, tray, &init_params);
     94     bubble_wrapper_.reset(new TrayBubbleWrapper(tray, bubble_view));
     95     bubble_view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
     96     bubble->InitializeContents(bubble_view);
     97   }
     98 
     99   message_center::MessageBubbleBase* bubble() const { return bubble_.get(); }
    100 
    101   // Convenience accessors.
    102   views::TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }
    103 
    104  private:
    105   scoped_ptr<message_center::MessageBubbleBase> bubble_;
    106   scoped_ptr<TrayBubbleWrapper> bubble_wrapper_;
    107 
    108   DISALLOW_COPY_AND_ASSIGN(WebNotificationBubbleWrapper);
    109 };
    110 
    111 class WebNotificationButton : public views::CustomButton {
    112  public:
    113   WebNotificationButton(views::ButtonListener* listener)
    114       : views::CustomButton(listener),
    115         is_bubble_visible_(false),
    116         unread_count_(0) {
    117     SetLayoutManager(new views::FillLayout);
    118     unread_label_ = new views::Label();
    119     SetupLabelForTray(unread_label_);
    120     AddChildView(unread_label_);
    121   }
    122 
    123   void SetBubbleVisible(bool visible) {
    124     if (visible == is_bubble_visible_)
    125       return;
    126 
    127     is_bubble_visible_ = visible;
    128     UpdateIconVisibility();
    129   }
    130 
    131   void SetUnreadCount(int unread_count) {
    132     // base::FormatNumber doesn't convert to arabic numeric characters.
    133     // TODO(mukai): use ICU to support conversion for such locales.
    134     unread_count_ = unread_count;
    135     // TODO(mukai): move NINE_PLUS message to ui_strings, it doesn't need to be
    136     // in ash_strings.
    137     unread_label_->SetText((unread_count > 9) ?
    138         l10n_util::GetStringUTF16(IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS) :
    139         base::FormatNumber(unread_count));
    140     UpdateIconVisibility();
    141   }
    142 
    143  protected:
    144   // Overridden from views::ImageButton:
    145   virtual gfx::Size GetPreferredSize() const OVERRIDE {
    146     return gfx::Size(kShelfItemHeight, kShelfItemHeight);
    147   }
    148 
    149   virtual int GetHeightForWidth(int width) const OVERRIDE {
    150     return GetPreferredSize().height();
    151   }
    152 
    153  private:
    154   void UpdateIconVisibility() {
    155     unread_label_->SetEnabledColor(
    156         (!is_bubble_visible_ && unread_count_ > 0) ?
    157         kWebNotificationColorWithUnread : kWebNotificationColorNoUnread);
    158     SchedulePaint();
    159   }
    160 
    161   bool is_bubble_visible_;
    162   int unread_count_;
    163 
    164   views::Label* unread_label_;
    165 
    166   DISALLOW_COPY_AND_ASSIGN(WebNotificationButton);
    167 };
    168 
    169 WebNotificationTray::WebNotificationTray(StatusAreaWidget* status_area_widget)
    170     : TrayBackgroundView(status_area_widget),
    171       button_(NULL),
    172       show_message_center_on_unlock_(false),
    173       should_update_tray_content_(false),
    174       should_block_shelf_auto_hide_(false) {
    175   button_ = new WebNotificationButton(this);
    176   button_->set_triggerable_event_flags(
    177       ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON);
    178   tray_container()->AddChildView(button_);
    179   SetContentsBackground();
    180   tray_container()->SetBorder(views::Border::NullBorder());
    181   message_center_tray_.reset(new message_center::MessageCenterTray(
    182       this,
    183       message_center::MessageCenter::Get()));
    184   popup_alignment_delegate_.reset(new AshPopupAlignmentDelegate());
    185   popup_collection_.reset(new message_center::MessagePopupCollection(
    186       ash::Shell::GetContainer(
    187           status_area_widget->GetNativeView()->GetRootWindow(),
    188           kShellWindowId_StatusContainer),
    189       message_center(),
    190       message_center_tray_.get(),
    191       popup_alignment_delegate_.get()));
    192   const gfx::Display& display = Shell::GetScreen()->GetDisplayNearestWindow(
    193       status_area_widget->GetNativeView());
    194   popup_alignment_delegate_->StartObserving(Shell::GetScreen(), display);
    195   OnMessageCenterTrayChanged();
    196 }
    197 
    198 WebNotificationTray::~WebNotificationTray() {
    199   // Release any child views that might have back pointers before ~View().
    200   message_center_bubble_.reset();
    201   popup_alignment_delegate_.reset();
    202   popup_collection_.reset();
    203 }
    204 
    205 // Public methods.
    206 
    207 bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) {
    208   if (!ShouldShowMessageCenter())
    209     return false;
    210 
    211   should_block_shelf_auto_hide_ = true;
    212   message_center::MessageCenterBubble* message_center_bubble =
    213       new message_center::MessageCenterBubble(
    214           message_center(),
    215           message_center_tray_.get(),
    216           true);
    217 
    218   int max_height = 0;
    219   aura::Window* status_area_window = status_area_widget()->GetNativeView();
    220   switch (GetShelfLayoutManager()->GetAlignment()) {
    221     case SHELF_ALIGNMENT_BOTTOM: {
    222       gfx::Rect shelf_bounds = GetShelfLayoutManager()->GetIdealBounds();
    223       max_height = shelf_bounds.y();
    224       break;
    225     }
    226     case SHELF_ALIGNMENT_TOP: {
    227       aura::Window* root = status_area_window->GetRootWindow();
    228       max_height =
    229           root->bounds().height() - status_area_window->bounds().height();
    230       break;
    231     }
    232     case SHELF_ALIGNMENT_LEFT:
    233     case SHELF_ALIGNMENT_RIGHT: {
    234       // Assume that the bottom line of the status area widget and the bubble
    235       // are aligned.
    236       max_height = status_area_window->GetBoundsInRootWindow().bottom();
    237       break;
    238     }
    239     default:
    240       NOTREACHED();
    241   }
    242 
    243   message_center_bubble->SetMaxHeight(std::max(0,
    244                                                max_height - kTraySpacing));
    245   if (show_settings)
    246     message_center_bubble->SetSettingsVisible();
    247   message_center_bubble_.reset(
    248       new WebNotificationBubbleWrapper(this, message_center_bubble));
    249 
    250   status_area_widget()->SetHideSystemNotifications(true);
    251   GetShelfLayoutManager()->UpdateAutoHideState();
    252   button_->SetBubbleVisible(true);
    253   SetDrawBackgroundAsActive(true);
    254   return true;
    255 }
    256 
    257 bool WebNotificationTray::ShowMessageCenter() {
    258   return ShowMessageCenterInternal(false /* show_settings */);
    259 }
    260 
    261 void WebNotificationTray::HideMessageCenter() {
    262   if (!message_center_bubble())
    263     return;
    264   SetDrawBackgroundAsActive(false);
    265   message_center_bubble_.reset();
    266   should_block_shelf_auto_hide_ = false;
    267   show_message_center_on_unlock_ = false;
    268   status_area_widget()->SetHideSystemNotifications(false);
    269   GetShelfLayoutManager()->UpdateAutoHideState();
    270   button_->SetBubbleVisible(false);
    271 }
    272 
    273 void WebNotificationTray::SetSystemTrayHeight(int height) {
    274   popup_alignment_delegate_->SetSystemTrayHeight(height);
    275 }
    276 
    277 bool WebNotificationTray::ShowPopups() {
    278   if (message_center_bubble())
    279     return false;
    280 
    281   popup_collection_->DoUpdateIfPossible();
    282   return true;
    283 }
    284 
    285 void WebNotificationTray::HidePopups() {
    286   DCHECK(popup_collection_.get());
    287   popup_collection_->MarkAllPopupsShown();
    288 }
    289 
    290 // Private methods.
    291 
    292 bool WebNotificationTray::ShouldShowMessageCenter() {
    293   return status_area_widget()->login_status() != user::LOGGED_IN_LOCKED &&
    294       !(status_area_widget()->system_tray() &&
    295         status_area_widget()->system_tray()->HasNotificationBubble());
    296 }
    297 
    298 bool WebNotificationTray::ShouldBlockShelfAutoHide() const {
    299   return should_block_shelf_auto_hide_;
    300 }
    301 
    302 bool WebNotificationTray::IsMessageCenterBubbleVisible() const {
    303   return (message_center_bubble() &&
    304           message_center_bubble()->bubble()->IsVisible());
    305 }
    306 
    307 bool WebNotificationTray::IsMouseInNotificationBubble() const {
    308   return false;
    309 }
    310 
    311 void WebNotificationTray::ShowMessageCenterBubble() {
    312   if (!IsMessageCenterBubbleVisible())
    313     message_center_tray_->ShowMessageCenterBubble();
    314 }
    315 
    316 void WebNotificationTray::UpdateAfterLoginStatusChange(
    317     user::LoginStatus login_status) {
    318   OnMessageCenterTrayChanged();
    319 }
    320 
    321 void WebNotificationTray::SetShelfAlignment(ShelfAlignment alignment) {
    322   if (alignment == shelf_alignment())
    323     return;
    324   TrayBackgroundView::SetShelfAlignment(alignment);
    325   tray_container()->SetBorder(views::Border::NullBorder());
    326   // Destroy any existing bubble so that it will be rebuilt correctly.
    327   message_center_tray_->HideMessageCenterBubble();
    328   message_center_tray_->HidePopupBubble();
    329 }
    330 
    331 void WebNotificationTray::AnchorUpdated() {
    332   if (message_center_bubble()) {
    333     message_center_bubble()->bubble_view()->UpdateBubble();
    334     UpdateBubbleViewArrow(message_center_bubble()->bubble_view());
    335   }
    336 }
    337 
    338 base::string16 WebNotificationTray::GetAccessibleNameForTray() {
    339   return l10n_util::GetStringUTF16(
    340       IDS_MESSAGE_CENTER_ACCESSIBLE_NAME);
    341 }
    342 
    343 void WebNotificationTray::HideBubbleWithView(
    344     const views::TrayBubbleView* bubble_view) {
    345   if (message_center_bubble() &&
    346       bubble_view == message_center_bubble()->bubble_view()) {
    347     message_center_tray_->HideMessageCenterBubble();
    348   } else if (popup_collection_.get()) {
    349     message_center_tray_->HidePopupBubble();
    350   }
    351 }
    352 
    353 bool WebNotificationTray::PerformAction(const ui::Event& event) {
    354   if (message_center_bubble())
    355     message_center_tray_->HideMessageCenterBubble();
    356   else
    357     message_center_tray_->ShowMessageCenterBubble();
    358   return true;
    359 }
    360 
    361 void WebNotificationTray::BubbleViewDestroyed() {
    362   if (message_center_bubble())
    363     message_center_bubble()->bubble()->BubbleViewDestroyed();
    364 }
    365 
    366 void WebNotificationTray::OnMouseEnteredView() {}
    367 
    368 void WebNotificationTray::OnMouseExitedView() {}
    369 
    370 base::string16 WebNotificationTray::GetAccessibleNameForBubble() {
    371   return GetAccessibleNameForTray();
    372 }
    373 
    374 gfx::Rect WebNotificationTray::GetAnchorRect(
    375     views::Widget* anchor_widget,
    376     views::TrayBubbleView::AnchorType anchor_type,
    377     views::TrayBubbleView::AnchorAlignment anchor_alignment) const {
    378   return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
    379 }
    380 
    381 void WebNotificationTray::HideBubble(const views::TrayBubbleView* bubble_view) {
    382   HideBubbleWithView(bubble_view);
    383 }
    384 
    385 bool WebNotificationTray::ShowNotifierSettings() {
    386   if (message_center_bubble()) {
    387     static_cast<message_center::MessageCenterBubble*>(
    388         message_center_bubble()->bubble())->SetSettingsVisible();
    389     return true;
    390   }
    391   return ShowMessageCenterInternal(true /* show_settings */);
    392 }
    393 
    394 bool WebNotificationTray::IsContextMenuEnabled() const {
    395   user::LoginStatus login_status = status_area_widget()->login_status();
    396   bool userAddingRunning = ash::Shell::GetInstance()
    397                                ->session_state_delegate()
    398                                ->IsInSecondaryLoginScreen();
    399 
    400   return login_status != user::LOGGED_IN_NONE
    401       && login_status != user::LOGGED_IN_LOCKED && !userAddingRunning;
    402 }
    403 
    404 message_center::MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
    405   return message_center_tray_.get();
    406 }
    407 
    408 bool WebNotificationTray::IsCommandIdChecked(int command_id) const {
    409   if (command_id != kToggleQuietMode)
    410     return false;
    411   return message_center()->IsQuietMode();
    412 }
    413 
    414 bool WebNotificationTray::IsCommandIdEnabled(int command_id) const {
    415   return true;
    416 }
    417 
    418 bool WebNotificationTray::GetAcceleratorForCommandId(
    419     int command_id,
    420     ui::Accelerator* accelerator) {
    421   return false;
    422 }
    423 
    424 void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) {
    425   if (command_id == kToggleQuietMode) {
    426     bool in_quiet_mode = message_center()->IsQuietMode();
    427     message_center()->SetQuietMode(!in_quiet_mode);
    428     return;
    429   }
    430   base::TimeDelta expires_in = command_id == kEnableQuietModeDay ?
    431       base::TimeDelta::FromDays(1):
    432       base::TimeDelta::FromHours(1);
    433   message_center()->EnterQuietModeWithExpire(expires_in);
    434 }
    435 
    436 void WebNotificationTray::ButtonPressed(views::Button* sender,
    437                                         const ui::Event& event) {
    438   DCHECK_EQ(button_, sender);
    439   PerformAction(event);
    440 }
    441 
    442 void WebNotificationTray::OnMessageCenterTrayChanged() {
    443   // Do not update the tray contents directly. Multiple change events can happen
    444   // consecutively, and calling Update in the middle of those events will show
    445   // intermediate unread counts for a moment.
    446   should_update_tray_content_ = true;
    447   base::MessageLoop::current()->PostTask(
    448       FROM_HERE,
    449       base::Bind(&WebNotificationTray::UpdateTrayContent, AsWeakPtr()));
    450 }
    451 
    452 void WebNotificationTray::UpdateTrayContent() {
    453   if (!should_update_tray_content_)
    454     return;
    455   should_update_tray_content_ = false;
    456 
    457   message_center::MessageCenter* message_center =
    458       message_center_tray_->message_center();
    459   button_->SetUnreadCount(message_center->UnreadNotificationCount());
    460   if (IsMessageCenterBubbleVisible())
    461     button_->SetState(views::CustomButton::STATE_PRESSED);
    462   else
    463     button_->SetState(views::CustomButton::STATE_NORMAL);
    464   bool userAddingRunning = ash::Shell::GetInstance()
    465                                ->session_state_delegate()
    466                                ->IsInSecondaryLoginScreen();
    467 
    468   SetVisible((status_area_widget()->login_status() != user::LOGGED_IN_NONE) &&
    469              (status_area_widget()->login_status() != user::LOGGED_IN_LOCKED) &&
    470              !userAddingRunning && (message_center->NotificationCount() > 0));
    471   Layout();
    472   SchedulePaint();
    473 }
    474 
    475 bool WebNotificationTray::ClickedOutsideBubble() {
    476   // Only hide the message center
    477   if (!message_center_bubble())
    478     return false;
    479 
    480   message_center_tray_->HideMessageCenterBubble();
    481   return true;
    482 }
    483 
    484 message_center::MessageCenter* WebNotificationTray::message_center() const {
    485   return message_center_tray_->message_center();
    486 }
    487 
    488 // Methods for testing
    489 
    490 bool WebNotificationTray::IsPopupVisible() const {
    491   return message_center_tray_->popups_visible();
    492 }
    493 
    494 message_center::MessageCenterBubble*
    495 WebNotificationTray::GetMessageCenterBubbleForTest() {
    496   if (!message_center_bubble())
    497     return NULL;
    498   return static_cast<message_center::MessageCenterBubble*>(
    499       message_center_bubble()->bubble());
    500 }
    501 
    502 }  // namespace ash
    503