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