• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CreateMessageCenterTray()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_|.
WebNotificationBubbleWrapper(WebNotificationTray * tray,message_center::MessageBubbleBase * bubble)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 
bubble() const99   message_center::MessageBubbleBase* bubble() const { return bubble_.get(); }
100 
101   // Convenience accessors.
bubble_view() const102   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:
WebNotificationButton(views::ButtonListener * listener)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 
SetBubbleVisible(bool visible)123   void SetBubbleVisible(bool visible) {
124     if (visible == is_bubble_visible_)
125       return;
126 
127     is_bubble_visible_ = visible;
128     UpdateIconVisibility();
129   }
130 
SetUnreadCount(int unread_count)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:
GetPreferredSize() const145   virtual gfx::Size GetPreferredSize() const OVERRIDE {
146     return gfx::Size(kShelfItemHeight, kShelfItemHeight);
147   }
148 
GetHeightForWidth(int width) const149   virtual int GetHeightForWidth(int width) const OVERRIDE {
150     return GetPreferredSize().height();
151   }
152 
153  private:
UpdateIconVisibility()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 
WebNotificationTray(StatusAreaWidget * status_area_widget)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 
~WebNotificationTray()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 
ShowMessageCenterInternal(bool show_settings)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 
ShowMessageCenter()257 bool WebNotificationTray::ShowMessageCenter() {
258   return ShowMessageCenterInternal(false /* show_settings */);
259 }
260 
HideMessageCenter()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 
SetSystemTrayHeight(int height)273 void WebNotificationTray::SetSystemTrayHeight(int height) {
274   popup_alignment_delegate_->SetSystemTrayHeight(height);
275 }
276 
ShowPopups()277 bool WebNotificationTray::ShowPopups() {
278   if (message_center_bubble())
279     return false;
280 
281   popup_collection_->DoUpdateIfPossible();
282   return true;
283 }
284 
HidePopups()285 void WebNotificationTray::HidePopups() {
286   DCHECK(popup_collection_.get());
287   popup_collection_->MarkAllPopupsShown();
288 }
289 
290 // Private methods.
291 
ShouldShowMessageCenter()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 
ShouldBlockShelfAutoHide() const298 bool WebNotificationTray::ShouldBlockShelfAutoHide() const {
299   return should_block_shelf_auto_hide_;
300 }
301 
IsMessageCenterBubbleVisible() const302 bool WebNotificationTray::IsMessageCenterBubbleVisible() const {
303   return (message_center_bubble() &&
304           message_center_bubble()->bubble()->IsVisible());
305 }
306 
IsMouseInNotificationBubble() const307 bool WebNotificationTray::IsMouseInNotificationBubble() const {
308   return false;
309 }
310 
ShowMessageCenterBubble()311 void WebNotificationTray::ShowMessageCenterBubble() {
312   if (!IsMessageCenterBubbleVisible())
313     message_center_tray_->ShowMessageCenterBubble();
314 }
315 
UpdateAfterLoginStatusChange(user::LoginStatus login_status)316 void WebNotificationTray::UpdateAfterLoginStatusChange(
317     user::LoginStatus login_status) {
318   OnMessageCenterTrayChanged();
319 }
320 
SetShelfAlignment(ShelfAlignment alignment)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 
AnchorUpdated()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 
GetAccessibleNameForTray()338 base::string16 WebNotificationTray::GetAccessibleNameForTray() {
339   return l10n_util::GetStringUTF16(
340       IDS_MESSAGE_CENTER_ACCESSIBLE_NAME);
341 }
342 
HideBubbleWithView(const views::TrayBubbleView * bubble_view)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 
PerformAction(const ui::Event & event)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 
BubbleViewDestroyed()361 void WebNotificationTray::BubbleViewDestroyed() {
362   if (message_center_bubble())
363     message_center_bubble()->bubble()->BubbleViewDestroyed();
364 }
365 
OnMouseEnteredView()366 void WebNotificationTray::OnMouseEnteredView() {}
367 
OnMouseExitedView()368 void WebNotificationTray::OnMouseExitedView() {}
369 
GetAccessibleNameForBubble()370 base::string16 WebNotificationTray::GetAccessibleNameForBubble() {
371   return GetAccessibleNameForTray();
372 }
373 
GetAnchorRect(views::Widget * anchor_widget,views::TrayBubbleView::AnchorType anchor_type,views::TrayBubbleView::AnchorAlignment anchor_alignment) const374 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 
HideBubble(const views::TrayBubbleView * bubble_view)381 void WebNotificationTray::HideBubble(const views::TrayBubbleView* bubble_view) {
382   HideBubbleWithView(bubble_view);
383 }
384 
ShowNotifierSettings()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 
IsContextMenuEnabled() const394 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 
GetMessageCenterTray()404 message_center::MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
405   return message_center_tray_.get();
406 }
407 
IsCommandIdChecked(int command_id) const408 bool WebNotificationTray::IsCommandIdChecked(int command_id) const {
409   if (command_id != kToggleQuietMode)
410     return false;
411   return message_center()->IsQuietMode();
412 }
413 
IsCommandIdEnabled(int command_id) const414 bool WebNotificationTray::IsCommandIdEnabled(int command_id) const {
415   return true;
416 }
417 
GetAcceleratorForCommandId(int command_id,ui::Accelerator * accelerator)418 bool WebNotificationTray::GetAcceleratorForCommandId(
419     int command_id,
420     ui::Accelerator* accelerator) {
421   return false;
422 }
423 
ExecuteCommand(int command_id,int event_flags)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 
ButtonPressed(views::Button * sender,const ui::Event & event)436 void WebNotificationTray::ButtonPressed(views::Button* sender,
437                                         const ui::Event& event) {
438   DCHECK_EQ(button_, sender);
439   PerformAction(event);
440 }
441 
OnMessageCenterTrayChanged()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 
UpdateTrayContent()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 
ClickedOutsideBubble()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 
message_center() const484 message_center::MessageCenter* WebNotificationTray::message_center() const {
485   return message_center_tray_->message_center();
486 }
487 
488 // Methods for testing
489 
IsPopupVisible() const490 bool WebNotificationTray::IsPopupVisible() const {
491   return message_center_tray_->popups_visible();
492 }
493 
494 message_center::MessageCenterBubble*
GetMessageCenterBubbleForTest()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