diff options
| author | yoshiki <yoshiki@chromium.org> | 2016-02-24 21:52:13 -0800 |
|---|---|---|
| committer | Commit bot <commit-bot@chromium.org> | 2016-02-25 05:53:37 +0000 |
| commit | 35c9a3b689e257570a0ba08af1c6e0dc18ac3cc1 (patch) | |
| tree | e73d45cc3fd2f4dbb3f57656c387ebaa1547c616 | |
| parent | d1e6fe94b6268034c6f127f11f044ca949b4fdbf (diff) | |
| download | chromium_src-35c9a3b689e257570a0ba08af1c6e0dc18ac3cc1.zip chromium_src-35c9a3b689e257570a0ba08af1c6e0dc18ac3cc1.tar.gz chromium_src-35c9a3b689e257570a0ba08af1c6e0dc18ac3cc1.tar.bz2 | |
Implement Non-Closable Notification
This patch adds pinned notifications, which can't be removed by user from the message center. This feature is only for ChromeOS, since the message center doesn't exist on the other platforms.
All toasts must be closable. But if the closing toast has non-closable attribute, it'll be kept in the message center after closing.
BUG=565005
R=dewittj@chromium.org
Review URL: https://codereview.chromium.org/1645843003
Cr-Commit-Position: refs/heads/master@{#377517}
30 files changed, 506 insertions, 114 deletions
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc index b2eb9ca..7965068 100644 --- a/ash/accelerators/accelerator_controller_unittest.cc +++ b/ash/accelerators/accelerator_controller_unittest.cc @@ -1519,7 +1519,8 @@ class DeprecatedAcceleratorTester : public AcceleratorControllerTest { } void RemoveAllNotifications() const { - message_center()->RemoveAllNotifications(false); + message_center()->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); } message_center::MessageCenter* message_center() const { diff --git a/ash/content/display/screen_orientation_controller_chromeos_unittest.cc b/ash/content/display/screen_orientation_controller_chromeos_unittest.cc index 6ce5086..5a5b8ab 100644 --- a/ash/content/display/screen_orientation_controller_chromeos_unittest.cc +++ b/ash/content/display/screen_orientation_controller_chromeos_unittest.cc @@ -437,7 +437,8 @@ TEST_F(ScreenOrientationControllerTest, BlockRotationNotifications) { EXPECT_TRUE(message_center->HasPopupNotifications()); // Clear all notifications - message_center->RemoveAllNotifications(false); + message_center->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); EXPECT_EQ(0u, message_center->NotificationCount()); EXPECT_FALSE(message_center->HasPopupNotifications()); @@ -456,7 +457,8 @@ TEST_F(ScreenOrientationControllerTest, BlockRotationNotifications) { // Reset the screen rotation. SetInternalDisplayRotation(gfx::Display::ROTATE_0); // Clear all notifications - message_center->RemoveAllNotifications(false); + message_center->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); ASSERT_NE(gfx::Display::ROTATE_180, GetCurrentInternalDisplayRotation()); ASSERT_EQ(0u, message_center->NotificationCount()); ASSERT_FALSE(message_center->HasPopupNotifications()); diff --git a/ash/system/web_notification/web_notification_tray_unittest.cc b/ash/system/web_notification/web_notification_tray_unittest.cc index 55cd0ac..24243df 100644 --- a/ash/system/web_notification/web_notification_tray_unittest.cc +++ b/ash/system/web_notification/web_notification_tray_unittest.cc @@ -94,7 +94,8 @@ class WebNotificationTrayTest : public test::AshTestBase { ~WebNotificationTrayTest() override {} void TearDown() override { - GetMessageCenter()->RemoveAllNotifications(false); + GetMessageCenter()->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); test::AshTestBase::TearDown(); } diff --git a/chrome/browser/extensions/api/notifications/notifications_apitest.cc b/chrome/browser/extensions/api/notifications/notifications_apitest.cc index d53dfb3..ee6eef5 100644 --- a/chrome/browser/extensions/api/notifications/notifications_apitest.cc +++ b/chrome/browser/extensions/api/notifications/notifications_apitest.cc @@ -146,12 +146,14 @@ IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestByUser) { { ResultCatcher catcher; - g_browser_process->message_center()->RemoveAllNotifications(false); + g_browser_process->message_center()->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); } { ResultCatcher catcher; - g_browser_process->message_center()->RemoveAllNotifications(true); + g_browser_process->message_center()->RemoveAllNotifications( + true /* by_user */, message_center::MessageCenter::RemoveType::ALL); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); } } diff --git a/chrome/browser/notifications/message_center_notification_manager.cc b/chrome/browser/notifications/message_center_notification_manager.cc index 06ed30b..da307bb 100644 --- a/chrome/browser/notifications/message_center_notification_manager.cc +++ b/chrome/browser/notifications/message_center_notification_manager.cc @@ -278,7 +278,8 @@ bool MessageCenterNotificationManager::CancelAllByProfile( } void MessageCenterNotificationManager::CancelAll() { - message_center_->RemoveAllNotifications(/* by_user */ false); + message_center_->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); } //////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.mm b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.mm index 8ea62f2..03d1be7 100644 --- a/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.mm +++ b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.mm @@ -95,7 +95,8 @@ IN_PROC_BROWSER_TEST_P(QuitWithAppsControllerInteractiveTest, QuitBehavior) { // If notification was dismissed by click, show again on next quit. notification->delegate()->Click(); - message_center->RemoveAllNotifications(false); + message_center->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); EXPECT_FALSE(controller->ShouldQuit()); EXPECT_TRUE(AppWindowRegistryUtil::IsAppWindowVisibleInAnyProfile(0)); notification = g_browser_process->notification_ui_manager()->FindById( @@ -108,7 +109,8 @@ IN_PROC_BROWSER_TEST_P(QuitWithAppsControllerInteractiveTest, QuitBehavior) { // If notification is closed by user, don't show it next time. notification->delegate()->Close(true); - message_center->RemoveAllNotifications(false); + message_center->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); EXPECT_FALSE(controller->ShouldQuit()); EXPECT_TRUE(AppWindowRegistryUtil::IsAppWindowVisibleInAnyProfile(0)); notification = g_browser_process->notification_ui_manager()->FindById( @@ -150,7 +152,8 @@ IN_PROC_BROWSER_TEST_P(QuitWithAppsControllerInteractiveTest, QuitBehavior) { app_window->web_contents()); notification->delegate()->ButtonClick(0); destroyed_watcher.Wait(); - message_center->RemoveAllNotifications(false); + message_center->RemoveAllNotifications( + false /* by_user */, message_center::MessageCenter::RemoveType::ALL); EXPECT_FALSE(AppWindowRegistryUtil::IsAppWindowVisibleInAnyProfile(0)); quit_observer.Wait(); } diff --git a/ui/message_center/fake_message_center.cc b/ui/message_center/fake_message_center.cc index c994088..f2db0db 100644 --- a/ui/message_center/fake_message_center.cc +++ b/ui/message_center/fake_message_center.cc @@ -72,11 +72,7 @@ void FakeMessageCenter::RemoveNotification(const std::string& id, bool by_user) { } -void FakeMessageCenter::RemoveAllNotifications(bool by_user) { -} - -void FakeMessageCenter::RemoveAllVisibleNotifications(bool by_user) { -} +void FakeMessageCenter::RemoveAllNotifications(bool by_user, RemoveType type) {} void FakeMessageCenter::SetNotificationIcon(const std::string& notification_id, const gfx::Image& image) { diff --git a/ui/message_center/fake_message_center.h b/ui/message_center/fake_message_center.h index 003efbc..cfbf8e9 100644 --- a/ui/message_center/fake_message_center.h +++ b/ui/message_center/fake_message_center.h @@ -40,8 +40,7 @@ class FakeMessageCenter : public MessageCenter { scoped_ptr<Notification> new_notification) override; void RemoveNotification(const std::string& id, bool by_user) override; - void RemoveAllNotifications(bool by_user) override; - void RemoveAllVisibleNotifications(bool by_user) override; + void RemoveAllNotifications(bool by_user, RemoveType type) override; void SetNotificationIcon(const std::string& notification_id, const gfx::Image& image) override; diff --git a/ui/message_center/message_center.h b/ui/message_center/message_center.h index 00d9545..d6ce0c3 100644 --- a/ui/message_center/message_center.h +++ b/ui/message_center/message_center.h @@ -50,6 +50,13 @@ class NotifierSettingsProvider; class MESSAGE_CENTER_EXPORT MessageCenter { public: + enum class RemoveType { + // Remove all notifications. + ALL, + // Remove non-pinned notification (don't remove invisible ones). + NON_PINNED, + }; + // Creates the global message center object. static void Initialize(); @@ -102,8 +109,7 @@ class MESSAGE_CENTER_EXPORT MessageCenter { // Removes an existing notification. virtual void RemoveNotification(const std::string& id, bool by_user) = 0; - virtual void RemoveAllNotifications(bool by_user) = 0; - virtual void RemoveAllVisibleNotifications(bool by_user) = 0; + virtual void RemoveAllNotifications(bool by_user, RemoveType type) = 0; // Sets the icon image. Icon appears at the top-left of the notification. virtual void SetNotificationIcon(const std::string& notification_id, diff --git a/ui/message_center/message_center_impl.cc b/ui/message_center/message_center_impl.cc index ada49c8..07cbe10 100644 --- a/ui/message_center/message_center_impl.cc +++ b/ui/message_center/message_center_impl.cc @@ -643,23 +643,20 @@ void MessageCenterImpl::RemoveNotificationsForNotifierId( } } -void MessageCenterImpl::RemoveAllNotifications(bool by_user) { - // Using not |blockers_| but an empty list since it wants to remove literally - // all notifications. - RemoveNotifications(by_user, NotificationBlockers()); -} +void MessageCenterImpl::RemoveAllNotifications(bool by_user, RemoveType type) { + bool remove_pinned = (type == RemoveType::NON_PINNED); -void MessageCenterImpl::RemoveAllVisibleNotifications(bool by_user) { - RemoveNotifications(by_user, blockers_); -} + const NotificationBlockers& blockers = + (type == RemoveType::ALL ? NotificationBlockers() /* empty blockers */ + : blockers_ /* use default blockers */); -void MessageCenterImpl::RemoveNotifications( - bool by_user, - const NotificationBlockers& blockers) { const NotificationList::Notifications notifications = notification_list_->GetVisibleNotifications(blockers); std::set<std::string> ids; for (const auto& notification : notifications) { + if (!remove_pinned && notification->pinned()) + continue; + ids.insert(notification->id()); scoped_refptr<NotificationDelegate> delegate = notification->delegate(); if (delegate.get()) diff --git a/ui/message_center/message_center_impl.h b/ui/message_center/message_center_impl.h index f71e52b..03dc9bc 100644 --- a/ui/message_center/message_center_impl.h +++ b/ui/message_center/message_center_impl.h @@ -58,8 +58,7 @@ class MessageCenterImpl : public MessageCenter, void UpdateNotification(const std::string& old_id, scoped_ptr<Notification> new_notification) override; void RemoveNotification(const std::string& id, bool by_user) override; - void RemoveAllNotifications(bool by_user) override; - void RemoveAllVisibleNotifications(bool by_user) override; + void RemoveAllNotifications(bool by_user, RemoveType type) override; void SetNotificationIcon(const std::string& notification_id, const gfx::Image& image) override; void SetNotificationImage(const std::string& notification_id, @@ -116,7 +115,6 @@ class MessageCenterImpl : public MessageCenter, size_t unread_count; }; - void RemoveNotifications(bool by_user, const NotificationBlockers& blockers); void RemoveNotificationsForNotifierId(const NotifierId& notifier_id); scoped_ptr<NotificationList> notification_list_; diff --git a/ui/message_center/message_center_impl_unittest.cc b/ui/message_center/message_center_impl_unittest.cc index 2b405fe..bc3828b 100644 --- a/ui/message_center/message_center_impl_unittest.cc +++ b/ui/message_center/message_center_impl_unittest.cc @@ -559,9 +559,10 @@ TEST_F(MessageCenterImplTest, TotalNotificationBlocker) { EXPECT_TRUE(NotificationsContain(notifications, "id3")); EXPECT_TRUE(NotificationsContain(notifications, "id4")); - // RemoveAllVisibleNotifications should remove just visible notifications. + // Remove just visible notifications. blocker.SetNotificationsEnabled(false); - message_center()->RemoveAllVisibleNotifications(false /* by_user */); + message_center()->RemoveAllNotifications( + false /* by_user */, MessageCenter::RemoveType::NON_PINNED); EXPECT_EQ(0u, message_center()->NotificationCount()); blocker.SetNotificationsEnabled(true); EXPECT_EQ(2u, message_center()->NotificationCount()); @@ -571,9 +572,10 @@ TEST_F(MessageCenterImplTest, TotalNotificationBlocker) { EXPECT_TRUE(NotificationsContain(notifications, "id3")); EXPECT_FALSE(NotificationsContain(notifications, "id4")); - // And RemoveAllNotifications should remove all. + // And remove all including invisible notifications. blocker.SetNotificationsEnabled(false); - message_center()->RemoveAllNotifications(false /* by_user */); + message_center()->RemoveAllNotifications(false /* by_user */, + MessageCenter::RemoveType::ALL); EXPECT_EQ(0u, message_center()->NotificationCount()); } diff --git a/ui/message_center/notification.cc b/ui/message_center/notification.cc index 0b5a4dd..7c76b2c 100644 --- a/ui/message_center/notification.cc +++ b/ui/message_center/notification.cc @@ -34,6 +34,9 @@ RichNotificationData::RichNotificationData() progress(0), should_make_spoken_feedback_for_popup_updates(true), clickable(true), +#if defined(OS_CHROMEOS) + pinned(false), +#endif // defined(OS_CHROMEOS) renotify(false), silent(false) {} @@ -50,6 +53,9 @@ RichNotificationData::RichNotificationData(const RichNotificationData& other) should_make_spoken_feedback_for_popup_updates( other.should_make_spoken_feedback_for_popup_updates), clickable(other.clickable), +#if defined(OS_CHROMEOS) + pinned(other.pinned), +#endif // defined(OS_CHROMEOS) vibration_pattern(other.vibration_pattern), renotify(other.renotify), silent(other.silent) {} diff --git a/ui/message_center/notification.h b/ui/message_center/notification.h index b9cf129..1214ee2 100644 --- a/ui/message_center/notification.h +++ b/ui/message_center/notification.h @@ -53,6 +53,11 @@ class MESSAGE_CENTER_EXPORT RichNotificationData { std::vector<ButtonInfo> buttons; bool should_make_spoken_feedback_for_popup_updates; bool clickable; +#if defined(OS_CHROMEOS) + // Flag if the notification is pinned. If true, the notification is pinned + // and user can't remove it. + bool pinned; +#endif // defined(OS_CHROMEOS) std::vector<int> vibration_pattern; bool renotify; bool silent; @@ -213,6 +218,17 @@ class MESSAGE_CENTER_EXPORT Notification { optional_fields_.clickable = clickable; } + bool pinned() const { +#if defined(OS_CHROMEOS) + return optional_fields_.pinned; +#else + return false; +#endif // defined(OS_CHROMEOS) + } +#if defined(OS_CHROMEOS) + void set_pinned(bool pinned) { optional_fields_.pinned = pinned; } +#endif // defined(OS_CHROMEOS) + NotificationDelegate* delegate() const { return delegate_.get(); } const RichNotificationData& rich_notification_data() const { diff --git a/ui/message_center/views/message_center_button_bar.cc b/ui/message_center/views/message_center_button_bar.cc index 6a11fd5..fbd5859 100644 --- a/ui/message_center/views/message_center_button_bar.cc +++ b/ui/message_center/views/message_center_button_bar.cc @@ -267,6 +267,10 @@ void MessageCenterButtonBar::SetCloseAllButtonEnabled(bool enabled) { close_all_button_->SetEnabled(enabled); } +views::Button* MessageCenterButtonBar::GetCloseAllButtonForTest() const { + return close_all_button_; +} + void MessageCenterButtonBar::SetBackArrowVisible(bool visible) { if (title_arrow_) title_arrow_->SetVisible(visible); @@ -281,7 +285,7 @@ void MessageCenterButtonBar::ChildVisibilityChanged(views::View* child) { void MessageCenterButtonBar::ButtonPressed(views::Button* sender, const ui::Event& event) { if (sender == close_all_button_) { - message_center_view()->ClearAllNotifications(); + message_center_view()->ClearAllClosableNotifications(); } else if (sender == settings_button_ || sender == title_arrow_) { MessageCenterView* center_view = message_center_view(); center_view->SetSettingsVisible(!center_view->settings_visible()); diff --git a/ui/message_center/views/message_center_button_bar.h b/ui/message_center/views/message_center_button_bar.h index 74c7b84..e7932ab 100644 --- a/ui/message_center/views/message_center_button_bar.h +++ b/ui/message_center/views/message_center_button_bar.h @@ -7,6 +7,7 @@ #include "base/macros.h" #include "build/build_config.h" +#include "ui/message_center/message_center_export.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/button/image_button.h" #include "ui/views/view.h" @@ -43,6 +44,8 @@ class MessageCenterButtonBar : public views::View, // Sometimes we shouldn't see the close-all button. void SetCloseAllButtonEnabled(bool enabled); + MESSAGE_CENTER_EXPORT views::Button* GetCloseAllButtonForTest() const; + // Sometimes we shouldn't see the back arrow (not in settings). void SetBackArrowVisible(bool visible); diff --git a/ui/message_center/views/message_center_view.cc b/ui/message_center/views/message_center_view.cc index dc6d527..a95de2b 100644 --- a/ui/message_center/views/message_center_view.cc +++ b/ui/message_center/views/message_center_view.cc @@ -255,20 +255,25 @@ void MessageCenterView::SetSettingsVisible(bool visible) { button_bar_->SetBackArrowVisible(visible); } -void MessageCenterView::ClearAllNotifications() { +void MessageCenterView::ClearAllClosableNotifications() { if (is_closing_) return; SetViewHierarchyEnabled(scroller_, false); button_bar_->SetAllButtonsEnabled(false); - message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect()); + message_list_view_->ClearAllClosableNotifications( + scroller_->GetVisibleRect()); } void MessageCenterView::OnAllNotificationsCleared() { SetViewHierarchyEnabled(scroller_, true); button_bar_->SetAllButtonsEnabled(true); button_bar_->SetCloseAllButtonEnabled(false); - message_center_->RemoveAllVisibleNotifications(true); // Action by user. + + // Action by user. + message_center_->RemoveAllNotifications( + true /* by_user */, + message_center::MessageCenter::RemoveType::NON_PINNED); } size_t MessageCenterView::NumMessageViewsForTest() const { @@ -588,7 +593,14 @@ void MessageCenterView::NotificationsChanged() { scroller_->contents()->AddChildView( no_message_views ? empty_list_view_.get() : message_list_view_.get()); - button_bar_->SetCloseAllButtonEnabled(!no_message_views); + bool no_closable_views = true; + for (const auto& view : notification_views_) { + if (!view.second->IsPinned()) { + no_closable_views = false; + break; + } + } + button_bar_->SetCloseAllButtonEnabled(!no_closable_views); scroller_->SetFocusable(!no_message_views); if (focus_manager && focused_view) diff --git a/ui/message_center/views/message_center_view.h b/ui/message_center/views/message_center_view.h index af91c4a..69d890d 100644 --- a/ui/message_center/views/message_center_view.h +++ b/ui/message_center/views/message_center_view.h @@ -50,7 +50,7 @@ class MESSAGE_CENTER_EXPORT MessageCenterView : public views::View, void SetNotifications(const NotificationList::Notifications& notifications); - void ClearAllNotifications(); + void ClearAllClosableNotifications(); void OnAllNotificationsCleared(); size_t NumMessageViewsForTest() const; diff --git a/ui/message_center/views/message_center_view_unittest.cc b/ui/message_center/views/message_center_view_unittest.cc index d412cb0..1d592a3 100644 --- a/ui/message_center/views/message_center_view_unittest.cc +++ b/ui/message_center/views/message_center_view_unittest.cc @@ -17,9 +17,11 @@ #include "ui/message_center/notification.h" #include "ui/message_center/notification_list.h" #include "ui/message_center/notification_types.h" +#include "ui/message_center/views/message_center_button_bar.h" #include "ui/message_center/views/message_center_controller.h" #include "ui/message_center/views/message_list_view.h" #include "ui/message_center/views/notification_view.h" +#include "ui/views/controls/slide_out_view.h" namespace message_center { @@ -34,6 +36,12 @@ enum CallType { LAYOUT }; +class DummyEvent : public ui::Event { + public: + DummyEvent() : Event(ui::ET_UNKNOWN, base::TimeDelta(), 0) {} + ~DummyEvent() override {} +}; + /* Instrumented/Mock NotificationView subclass ********************************/ class MockNotificationView : public NotificationView { @@ -94,6 +102,11 @@ class FakeMessageCenterImpl : public FakeMessageCenter { void SetVisibleNotifications(NotificationList::Notifications notifications) { visible_notifications_ = notifications; } + void RemoveAllNotifications(bool by_user, RemoveType type) override { + if (type == RemoveType::NON_PINNED) + remove_all_closable_notification_called_ = true; + } + bool remove_all_closable_notification_called_ = false; NotificationList::Notifications visible_notifications_; }; @@ -111,6 +124,7 @@ class MessageCenterViewTest : public testing::Test, MessageCenterView* GetMessageCenterView(); MessageListView* GetMessageListView(); + FakeMessageCenterImpl* GetMessageCenter() const; NotificationView* GetNotificationView(const std::string& id); views::BoundsAnimator* GetAnimator(); int GetNotificationCount(); @@ -139,6 +153,8 @@ class MessageCenterViewTest : public testing::Test, void LogBounds(int depth, views::View* view); + MessageCenterButtonBar* GetButtonBar() const; + private: views::View* MakeParent(views::View* child1, views::View* child2); @@ -146,7 +162,7 @@ class MessageCenterViewTest : public testing::Test, NotificationList::Notifications notifications_; scoped_ptr<MessageCenterView> message_center_view_; - FakeMessageCenterImpl message_center_; + scoped_ptr<FakeMessageCenterImpl> message_center_; std::map<CallType,int> callCounts_; DISALLOW_COPY_AND_ASSIGN(MessageCenterViewTest); @@ -159,6 +175,8 @@ MessageCenterViewTest::~MessageCenterViewTest() { } void MessageCenterViewTest::SetUp() { + message_center_.reset(new FakeMessageCenterImpl()); + // Create a dummy notification. Notification* notification1 = new Notification( NOTIFICATION_TYPE_SIMPLE, std::string(kNotificationId1), @@ -177,12 +195,12 @@ void MessageCenterViewTest::SetUp() { // ...and a list for it. notifications_.insert(notification1); notifications_.insert(notification2); - message_center_.SetVisibleNotifications(notifications_); + message_center_->SetVisibleNotifications(notifications_); // Then create a new MessageCenterView with that single notification. base::string16 title; message_center_view_.reset(new MessageCenterView( - &message_center_, NULL, 100, false, /*top_down =*/false, title)); + message_center_.get(), NULL, 100, false, /*top_down =*/false, title)); GetMessageListView()->quit_message_loop_after_animation_for_test_ = true; GetMessageCenterView()->SetBounds(0, 0, 380, 600); message_center_view_->SetNotifications(notifications_); @@ -205,6 +223,10 @@ MessageListView* MessageCenterViewTest::GetMessageListView() { return message_center_view_->message_list_view_.get(); } +FakeMessageCenterImpl* MessageCenterViewTest::GetMessageCenter() const { + return message_center_.get(); +} + NotificationView* MessageCenterViewTest::GetNotificationView( const std::string& id) { return message_center_view_->notification_views_[id]; @@ -236,7 +258,7 @@ void MessageCenterViewTest::AddNotification( scoped_ptr<Notification> notification) { std::string notification_id = notification->id(); notifications_.insert(notification.release()); - message_center_.SetVisibleNotifications(notifications_); + message_center_->SetVisibleNotifications(notifications_); message_center_view_->OnNotificationAdded(notification_id); } @@ -253,7 +275,7 @@ void MessageCenterViewTest::UpdateNotification( // |notifications| is a "set" container so we don't need to be aware the // order. notifications_.insert(notification.release()); - message_center_.SetVisibleNotifications(notifications_); + message_center_->SetVisibleNotifications(notifications_); message_center_view_->OnNotificationUpdated(notification_id); } @@ -267,7 +289,7 @@ void MessageCenterViewTest::RemoveNotification( break; } } - message_center_.SetVisibleNotifications(notifications_); + message_center_->SetVisibleNotifications(notifications_); message_center_view_->OnNotificationRemoved(notification_id, by_user); } @@ -322,6 +344,10 @@ void MessageCenterViewTest::LogBounds(int depth, views::View* view) { LogBounds(depth + 1, view->child_at(i)); } +MessageCenterButtonBar* MessageCenterViewTest::GetButtonBar() const { + return message_center_view_->button_bar_; +} + /* Unit tests *****************************************************************/ TEST_F(MessageCenterViewTest, CallTest) { @@ -488,4 +514,89 @@ TEST_F(MessageCenterViewTest, PositionAfterRemove) { GetMessageListView()->height()); } +TEST_F(MessageCenterViewTest, CloseButton) { + views::Button* close_button = GetButtonBar()->GetCloseAllButtonForTest(); + EXPECT_NE(nullptr, close_button); + + ((views::ButtonListener*)GetButtonBar()) + ->ButtonPressed(close_button, DummyEvent()); + base::MessageLoop::current()->Run(); + EXPECT_TRUE(GetMessageCenter()->remove_all_closable_notification_called_); +} + +TEST_F(MessageCenterViewTest, CloseButtonEnablity) { + views::Button* close_button = GetButtonBar()->GetCloseAllButtonForTest(); + EXPECT_NE(nullptr, close_button); + + // There should be 2 non-pinned notifications. + EXPECT_EQ(2u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_TRUE(close_button->enabled()); + + RemoveNotification(kNotificationId1, false); + base::MessageLoop::current()->Run(); + + // There should be 1 non-pinned notification. + EXPECT_EQ(1u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_TRUE(close_button->enabled()); + + RemoveNotification(kNotificationId2, false); + base::MessageLoop::current()->Run(); + + // There should be no notification. + EXPECT_EQ(0u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_FALSE(close_button->enabled()); + + Notification normal_notification( + NOTIFICATION_TYPE_SIMPLE, std::string(kNotificationId1), + base::UTF8ToUTF16("title2"), + base::UTF8ToUTF16("message\nwhich\nis\nvertically\nlong\n."), + gfx::Image(), base::UTF8ToUTF16("display source"), GURL(), + NotifierId(NotifierId::APPLICATION, "extension_id"), + message_center::RichNotificationData(), NULL); + +#if defined(OS_CHROMEOS) + Notification pinned_notification( + NOTIFICATION_TYPE_SIMPLE, std::string(kNotificationId2), + base::UTF8ToUTF16("title2"), + base::UTF8ToUTF16("message\nwhich\nis\nvertically\nlong\n."), + gfx::Image(), base::UTF8ToUTF16("display source"), GURL(), + NotifierId(NotifierId::APPLICATION, "extension_id"), + message_center::RichNotificationData(), NULL); + pinned_notification.set_pinned(true); + + AddNotification( + scoped_ptr<Notification>(new Notification(normal_notification))); + + // There should be 1 non-pinned notification. + EXPECT_EQ(1u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_TRUE(close_button->enabled()); + + AddNotification( + scoped_ptr<Notification>(new Notification(pinned_notification))); + + // There should be 1 normal notification and 1 pinned notification. + EXPECT_EQ(2u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_TRUE(close_button->enabled()); + + RemoveNotification(kNotificationId1, false); + + // There should be 1 pinned notification. + EXPECT_EQ(1u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_FALSE(close_button->enabled()); + + RemoveNotification(kNotificationId2, false); + + // There should be no notification. + EXPECT_EQ(0u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_FALSE(close_button->enabled()); + + AddNotification( + scoped_ptr<Notification>(new Notification(pinned_notification))); + + // There should be 1 pinned notification. + EXPECT_EQ(1u, GetMessageCenter()->GetVisibleNotifications().size()); + EXPECT_FALSE(close_button->enabled()); +#endif // defined(OS_CHROMEOS) +} + } // namespace message_center diff --git a/ui/message_center/views/message_list_view.cc b/ui/message_center/views/message_list_view.cc index 9593330..7087998 100644 --- a/ui/message_center/views/message_list_view.cc +++ b/ui/message_center/views/message_list_view.cc @@ -191,17 +191,24 @@ void MessageListView::ResetRepositionSession() { fixed_height_ = 0; } -void MessageListView::ClearAllNotifications( +void MessageListView::ClearAllClosableNotifications( const gfx::Rect& visible_scroll_rect) { for (int i = 0; i < child_count(); ++i) { - views::View* child = child_at(i); + // Safe cast since all views in MessageListView are MessageViews. + MessageView* child = (MessageView*)child_at(i); if (!child->visible()) continue; if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty()) continue; + if (child->IsPinned()) + continue; clearing_all_views_.push_back(child); } - DoUpdateIfPossible(); + if (clearing_all_views_.empty()) { + message_center_view()->OnAllNotificationsCleared(); + } else { + DoUpdateIfPossible(); + } } void MessageListView::OnBoundsAnimatorProgressed( diff --git a/ui/message_center/views/message_list_view.h b/ui/message_center/views/message_list_view.h index 2909f20..1007af3 100644 --- a/ui/message_center/views/message_list_view.h +++ b/ui/message_center/views/message_list_view.h @@ -46,7 +46,7 @@ class MessageListView : public views::View, void UpdateNotification(MessageView* view, const Notification& notification); void SetRepositionTarget(const gfx::Rect& target_rect); void ResetRepositionSession(); - void ClearAllNotifications(const gfx::Rect& visible_scroll_rect); + void ClearAllClosableNotifications(const gfx::Rect& visible_scroll_rect); MESSAGE_CENTER_EXPORT void SetRepositionTargetForTest( const gfx::Rect& target_rect); diff --git a/ui/message_center/views/message_popup_collection.cc b/ui/message_center/views/message_popup_collection.cc index 1a28ffe..e4280aa 100644 --- a/ui/message_center/views/message_popup_collection.cc +++ b/ui/message_center/views/message_popup_collection.cc @@ -83,7 +83,25 @@ void MessagePopupCollection::ClickOnNotification( void MessagePopupCollection::RemoveNotification( const std::string& notification_id, bool by_user) { - message_center_->RemoveNotification(notification_id, by_user); + NotificationList::PopupNotifications notifications = + message_center_->GetPopupNotifications(); + for (NotificationList::PopupNotifications::iterator iter = + notifications.begin(); + iter != notifications.end(); ++iter) { + Notification* notification = *iter; + DCHECK(notification); + + if (notification->id() != notification_id) + continue; + + // Don't remove the notification only when it's not pinned. + if (!notification->pinned()) + message_center_->RemoveNotification(notification_id, by_user); + else + message_center_->MarkSinglePopupAsShown(notification_id, true /* read */); + + break; + } } scoped_ptr<ui::MenuModel> MessagePopupCollection::CreateMenuModel( @@ -139,10 +157,21 @@ void MessagePopupCollection::UpdateWidgets() { if (FindToast((*iter)->id())) continue; - NotificationView* view = - NotificationView::Create(NULL, - *(*iter), - true); // Create top-level notification. + NotificationView* view; + // Create top-level notification. +#if defined(OS_CHROMEOS) + if ((*iter)->pinned()) { + Notification notification = *(*iter); + // Override pinned status, since toasts should be closable even when it's + // pinned. + notification.set_pinned(false); + view = NotificationView::Create(NULL, notification, true); + } else +#endif // defined(OS_CHROMEOS) + { + view = NotificationView::Create(NULL, *(*iter), true); + } + view->set_context_menu_controller(context_menu_controller_.get()); int view_height = ToastContentsView::GetToastSizeForView(view).height(); int height_available = diff --git a/ui/message_center/views/message_popup_collection_unittest.cc b/ui/message_center/views/message_popup_collection_unittest.cc index e38bb4b..a41ae01 100644 --- a/ui/message_center/views/message_popup_collection_unittest.cc +++ b/ui/message_center/views/message_popup_collection_unittest.cc @@ -118,7 +118,8 @@ class MessagePopupCollectionTest : public views::ViewsTestBase { void CloseAllToasts() { // Assumes there is at least one toast to close. EXPECT_TRUE(GetToastCounts() > 0); - MessageCenter::Get()->RemoveAllNotifications(false); + MessageCenter::Get()->RemoveAllNotifications( + false /* by_user */, MessageCenter::RemoveType::ALL); } gfx::Rect GetToastRectAt(size_t index) { @@ -480,6 +481,44 @@ TEST_F(MessagePopupCollectionTest, ManyPopupNotifications) { WaitForTransitionsDone(); } +#if defined(OS_CHROMEOS) + +TEST_F(MessagePopupCollectionTest, CloseNonClosableNotifications) { + const char* kNotificationId = "NOTIFICATION1"; + + scoped_ptr<Notification> notification(new Notification( + NOTIFICATION_TYPE_BASE_FORMAT, kNotificationId, + base::UTF8ToUTF16("test title"), base::UTF8ToUTF16("test message"), + gfx::Image(), base::string16() /* display_source */, GURL(), + NotifierId(NotifierId::APPLICATION, kNotificationId), + message_center::RichNotificationData(), new NotificationDelegate())); + notification->set_pinned(true); + + // Add a pinned notification. + MessageCenter::Get()->AddNotification(std::move(notification)); + WaitForTransitionsDone(); + + // Confirms that there is a toast. + EXPECT_EQ(1u, GetToastCounts()); + EXPECT_EQ(1u, MessageCenter::Get()->NotificationCount()); + + // Close the toast. + views::WidgetDelegateView* toast1 = GetToast(kNotificationId); + ASSERT_TRUE(toast1 != NULL); + ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), + ui::EventTimeForNow(), 0, 0); + toast1->OnMouseEntered(event); + static_cast<MessageCenterObserver*>(collection()) + ->OnNotificationRemoved(kNotificationId, true); + WaitForTransitionsDone(); + + // Confirms that there is no toast. + EXPECT_EQ(0u, GetToastCounts()); + // But the notification still exists. + EXPECT_EQ(1u, MessageCenter::Get()->NotificationCount()); +} + +#endif // defined(OS_CHROMEOS) } // namespace test } // namespace message_center diff --git a/ui/message_center/views/message_view.cc b/ui/message_center/views/message_view.cc index 6f2597f..8047351 100644 --- a/ui/message_center/views/message_view.cc +++ b/ui/message_center/views/message_view.cc @@ -26,9 +26,6 @@ namespace { -const int kCloseIconTopPadding = 5; -const int kCloseIconRightPadding = 5; - const int kShadowOffset = 1; const int kShadowBlur = 4; @@ -63,19 +60,6 @@ MessageView::MessageView(MessageViewController* controller, small_image_view->set_owned_by_client(); small_image_view_.reset(small_image_view); - PaddedButton *close = new PaddedButton(this); - close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding); - close->SetNormalImage(IDR_NOTIFICATION_CLOSE); - close->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER); - close->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED); - close->set_animate_on_state_change(false); - close->SetAccessibleName(l10n_util::GetStringUTF16( - IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME)); - // The close button should be added to view hierarchy by the derived class. - // This ensures that it is on top of other views. - close->set_owned_by_client(); - close_button_.reset(close); - focus_painter_ = views::Painter::CreateSolidFocusPainter( kFocusBorderColor, gfx::Insets(0, 1, 3, 2)); } @@ -103,12 +87,16 @@ void MessageView::CreateShadowBorder() { } bool MessageView::IsCloseButtonFocused() { - views::FocusManager* focus_manager = GetFocusManager(); - return focus_manager && focus_manager->GetFocusedView() == close_button(); + // May be overridden by the owner of the close button. + return false; } void MessageView::RequestFocusOnCloseButton() { - close_button_->RequestFocus(); + // May be overridden by the owner of the close button. +} + +bool MessageView::IsPinned() { + return false; } void MessageView::GetAccessibleState(ui::AXViewState* state) { @@ -151,7 +139,6 @@ bool MessageView::OnKeyReleased(const ui::KeyEvent& event) { } void MessageView::OnPaint(gfx::Canvas* canvas) { - DCHECK_EQ(this, close_button_->parent()); DCHECK_EQ(this, small_image_view_->parent()); SlideOutView::OnPaint(canvas); views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get()); @@ -175,14 +162,6 @@ void MessageView::Layout() { // Background. background_view_->SetBoundsRect(content_bounds); - // Close button. - gfx::Size close_size(close_button_->GetPreferredSize()); - gfx::Rect close_rect(content_bounds.right() - close_size.width(), - content_bounds.y(), - close_size.width(), - close_size.height()); - close_button_->SetBoundsRect(close_rect); - gfx::Size small_image_size(small_image_view_->GetPreferredSize()); gfx::Rect small_image_rect(small_image_size); small_image_rect.set_origin(gfx::Point( @@ -229,9 +208,6 @@ void MessageView::OnGestureEvent(ui::GestureEvent* event) { void MessageView::ButtonPressed(views::Button* sender, const ui::Event& event) { - if (sender == close_button()) { - controller_->RemoveNotification(notification_id_, true); // By user. - } } void MessageView::OnSlideOut() { diff --git a/ui/message_center/views/message_view.h b/ui/message_center/views/message_view.h index 47840df..6a4754e 100644 --- a/ui/message_center/views/message_view.h +++ b/ui/message_center/views/message_view.h @@ -46,8 +46,8 @@ const int kPaddingHorizontal = 18; const int kWebNotificationButtonWidth = 32; const int kWebNotificationIconSize = 40; -// An base class for a notification entry. Contains background, close button -// and other elements shared by derived notification views. +// An base class for a notification entry. Contains background and other +// elements shared by derived notification views. class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView, public views::ButtonListener { public: @@ -67,8 +67,9 @@ class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView, // Creates a shadow around the notification. void CreateShadowBorder(); - bool IsCloseButtonFocused(); - void RequestFocusOnCloseButton(); + virtual bool IsCloseButtonFocused(); + virtual void RequestFocusOnCloseButton(); + virtual bool IsPinned(); void set_accessible_name(const base::string16& accessible_name) { accessible_name_ = accessible_name; @@ -100,7 +101,6 @@ class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView, void OnSlideOut() override; views::ImageView* small_image() { return small_image_view_.get(); } - views::ImageButton* close_button() { return close_button_.get(); } views::ScrollView* scroller() { return scroller_; } private: @@ -108,7 +108,6 @@ class MESSAGE_CENTER_EXPORT MessageView : public views::SlideOutView, std::string notification_id_; NotifierId notifier_id_; views::View* background_view_; // Owned by views hierarchy. - scoped_ptr<views::ImageButton> close_button_; scoped_ptr<views::ImageView> small_image_view_; views::ScrollView* scroller_; diff --git a/ui/message_center/views/notification_view.cc b/ui/message_center/views/notification_view.cc index fe91d1b..254fcbf 100644 --- a/ui/message_center/views/notification_view.cc +++ b/ui/message_center/views/notification_view.cc @@ -59,6 +59,9 @@ namespace { // Dimensions. const int kProgressBarBottomPadding = 0; +const int kCloseIconTopPadding = 5; +const int kCloseIconRightPadding = 5; + // static scoped_ptr<views::Border> MakeEmptyBorder(int top, int left, @@ -228,7 +231,8 @@ views::View* NotificationView::TargetForRect(views::View* root, action_buttons_.end()); if (settings_button_view_) buttons.push_back(settings_button_view_); - buttons.push_back(close_button()); + if (close_button_) + buttons.push_back(close_button_.get()); for (size_t i = 0; i < buttons.size(); ++i) { gfx::Point point_in_child = point; @@ -241,6 +245,7 @@ views::View* NotificationView::TargetForRect(views::View* root, } void NotificationView::CreateOrUpdateViews(const Notification& notification) { + CreateOrUpdateCloseButtonView(notification); CreateOrUpdateTitleView(notification); CreateOrUpdateMessageView(notification); CreateOrUpdateProgressBarView(notification); @@ -308,7 +313,6 @@ NotificationView::NotificationView(MessageCenterController* controller, // image to overlap the content as needed to provide large enough click and // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>). AddChildView(small_image()); - AddChildView(close_button()); SetAccessibleName(notification); SetEventTargeter( @@ -378,6 +382,16 @@ void NotificationView::Layout() { int top_height = top_view_->GetHeightForWidth(content_width); top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height); + // Close button. + if (close_button_) { + gfx::Rect content_bounds = GetContentsBounds(); + gfx::Size close_size(close_button_->GetPreferredSize()); + gfx::Rect close_rect(content_bounds.right() - close_size.width(), + content_bounds.y(), close_size.width(), + close_size.height()); + close_button_->SetBoundsRect(close_rect); + } + // Icon. icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize); @@ -445,6 +459,10 @@ void NotificationView::ButtonPressed(views::Button* sender, } } + if (close_button_ && sender == close_button_.get()) { + controller_->RemoveNotification(notification_id(), true); // By user. + } + // Let the superclass handle everything else. // Warning: This may cause the NotificationView itself to be deleted, // so don't do anything afterwards. @@ -772,6 +790,29 @@ void NotificationView::CreateOrUpdateActionButtonViews( } } +void NotificationView::CreateOrUpdateCloseButtonView( + const Notification& notification) { + set_slide_out_enabled(!notification.pinned()); + + if (!notification.pinned() && !close_button_) { + PaddedButton* close = new PaddedButton(this); + close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding); + close->SetNormalImage(IDR_NOTIFICATION_CLOSE); + close->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER); + close->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED); + close->set_animate_on_state_change(false); + close->SetAccessibleName(l10n_util::GetStringUTF16( + IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME)); + // The close button should be added to view hierarchy by the derived class. + // This ensures that it is on top of other views. + close->set_owned_by_client(); + close_button_.reset(close); + AddChildView(close_button_.get()); + } else if (notification.pinned() && close_button_) { + close_button_.reset(); + } +} + int NotificationView::GetMessageLineLimit(int title_lines, int width) const { // Image notifications require that the image must be kept flush against // their icons, but we can allow more text if no image. @@ -813,4 +854,22 @@ int NotificationView::GetMessageHeight(int width, int limit) const { message_view_->GetSizeForWidthAndLines(width, limit).height() : 0; } +bool NotificationView::IsCloseButtonFocused() { + if (!close_button_) + return false; + + views::FocusManager* focus_manager = GetFocusManager(); + return focus_manager && + focus_manager->GetFocusedView() == close_button_.get(); +} + +void NotificationView::RequestFocusOnCloseButton() { + if (close_button_) + close_button_->RequestFocus(); +} + +bool NotificationView::IsPinned() { + return !close_button_; +} + } // namespace message_center diff --git a/ui/message_center/views/notification_view.h b/ui/message_center/views/notification_view.h index 491080c..fd42a85 100644 --- a/ui/message_center/views/notification_view.h +++ b/ui/message_center/views/notification_view.h @@ -62,6 +62,9 @@ class MESSAGE_CENTER_EXPORT NotificationView // Overridden from MessageView: void UpdateWithNotification(const Notification& notification) override; void ButtonPressed(views::Button* sender, const ui::Event& event) override; + bool IsCloseButtonFocused() override; + void RequestFocusOnCloseButton() override; + bool IsPinned() override; // Overridden from MessageViewController: void ClickOnNotification(const std::string& notification_id) override; @@ -105,6 +108,7 @@ class MESSAGE_CENTER_EXPORT NotificationView void CreateOrUpdateIconView(const Notification& notification); void CreateOrUpdateImageView(const Notification& notification); void CreateOrUpdateActionButtonViews(const Notification& notification); + void CreateOrUpdateCloseButtonView(const Notification& notification); int GetMessageLineLimit(int title_lines, int width) const; int GetMessageHeight(int width, int limit) const; @@ -133,6 +137,7 @@ class MESSAGE_CENTER_EXPORT NotificationView ProportionalImageView* image_view_; NotificationProgressBarBase* progress_bar_view_; std::vector<NotificationButton*> action_buttons_; + scoped_ptr<views::ImageButton> close_button_; std::vector<views::View*> separators_; DISALLOW_COPY_AND_ASSIGN(NotificationView); diff --git a/ui/message_center/views/notification_view_unittest.cc b/ui/message_center/views/notification_view_unittest.cc index 383d198..bcaf421 100644 --- a/ui/message_center/views/notification_view_unittest.cc +++ b/ui/message_center/views/notification_view_unittest.cc @@ -11,6 +11,7 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" +#include "ui/compositor/scoped_animation_duration_scale_mode.h" #include "ui/events/event_processor.h" #include "ui/events/event_utils.h" #include "ui/gfx/canvas.h" @@ -31,6 +32,24 @@ #include "ui/views/test/widget_test.h" #include "ui/views/widget/widget_delegate.h" +namespace { + +scoped_ptr<ui::GestureEvent> GenerateGestureEvent(ui::EventType type) { + ui::GestureEventDetails detail(type); + scoped_ptr<ui::GestureEvent> event( + new ui::GestureEvent(0, 0, 0, base::TimeDelta(), detail)); + return event; +} + +scoped_ptr<ui::GestureEvent> GenerateGestureVerticalScrollUpdateEvent(int dx) { + ui::GestureEventDetails detail(ui::ET_GESTURE_SCROLL_UPDATE, dx, 0); + scoped_ptr<ui::GestureEvent> event( + new ui::GestureEvent(0, 0, 0, base::TimeDelta(), detail)); + return event; +} + +} // anonymouse namespace + namespace message_center { // A test delegate used for tests that deal with the notification settings @@ -54,7 +73,9 @@ class NotificationViewTest : public views::ViewsTestBase, void TearDown() override; views::Widget* widget() { return notification_view_->GetWidget(); } - NotificationView* notification_view() { return notification_view_.get(); } + NotificationView* notification_view() const { + return notification_view_.get(); + } Notification* notification() { return notification_.get(); } RichNotificationData* data() { return data_.get(); } @@ -155,12 +176,28 @@ class NotificationViewTest : public views::ViewsTestBase, } } + views::ImageButton* GetCloseButton() { + return notification_view()->close_button_.get(); + } + void UpdateNotificationViews() { notification_view()->CreateOrUpdateViews(*notification()); notification_view()->Layout(); } + float GetNotificationScrollAmount() const { + return notification_view()->GetTransform().To2dTranslation().x(); + } + + bool IsRemoved(const std::string& notification_id) const { + return (removed_ids_.find(notification_id) != removed_ids_.end()); + } + + void RemoveNotificationView() { notification_view_.reset(); } + private: + std::set<std::string> removed_ids_; + scoped_ptr<RichNotificationData> data_; scoped_ptr<Notification> notification_; scoped_ptr<NotificationView> notification_view_; @@ -216,8 +253,7 @@ void NotificationViewTest::ClickOnNotification( void NotificationViewTest::RemoveNotification( const std::string& notification_id, bool by_user) { - // For this test, this method should not be invoked. - NOTREACHED(); + removed_ids_.insert(notification_id); } scoped_ptr<ui::MenuModel> NotificationViewTest::CreateMenuModel( @@ -618,4 +654,64 @@ TEST_F(NotificationViewTest, FormatContextMessageTest) { EXPECT_TRUE(base::UTF16ToUTF8(result).find("hello") == std::string::npos); } +TEST_F(NotificationViewTest, SlideOut) { + ui::ScopedAnimationDurationScaleMode zero_duration_scope( + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); + + UpdateNotificationViews(); + std::string notification_id = notification()->id(); + + auto event_begin = GenerateGestureEvent(ui::ET_GESTURE_SCROLL_BEGIN); + auto event_scroll10 = GenerateGestureVerticalScrollUpdateEvent(-10); + auto event_scroll500 = GenerateGestureVerticalScrollUpdateEvent(-500); + auto event_end = GenerateGestureEvent(ui::ET_GESTURE_SCROLL_END); + + notification_view()->OnGestureEvent(event_begin.get()); + notification_view()->OnGestureEvent(event_scroll10.get()); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(-10.f, GetNotificationScrollAmount()); + notification_view()->OnGestureEvent(event_end.get()); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(0.f, GetNotificationScrollAmount()); + + notification_view()->OnGestureEvent(event_begin.get()); + notification_view()->OnGestureEvent(event_scroll500.get()); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_EQ(-500.f, GetNotificationScrollAmount()); + notification_view()->OnGestureEvent(event_end.get()); + EXPECT_TRUE(IsRemoved(notification_id)); +} + +// Pinning notification is ChromeOS only feature. +#if defined(OS_CHROMEOS) + +TEST_F(NotificationViewTest, SlideOutPinned) { + ui::ScopedAnimationDurationScaleMode zero_duration_scope( + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); + + notification()->set_pinned(true); + UpdateNotificationViews(); + std::string notification_id = notification()->id(); + + auto event_begin = GenerateGestureEvent(ui::ET_GESTURE_SCROLL_BEGIN); + auto event_scroll500 = GenerateGestureVerticalScrollUpdateEvent(-500); + auto event_end = GenerateGestureEvent(ui::ET_GESTURE_SCROLL_END); + + notification_view()->OnGestureEvent(event_begin.get()); + notification_view()->OnGestureEvent(event_scroll500.get()); + EXPECT_FALSE(IsRemoved(notification_id)); + EXPECT_LT(-500.f, GetNotificationScrollAmount()); + notification_view()->OnGestureEvent(event_end.get()); + EXPECT_FALSE(IsRemoved(notification_id)); +} + +TEST_F(NotificationViewTest, Pinned) { + notification()->set_pinned(true); + + UpdateNotificationViews(); + EXPECT_EQ(NULL, GetCloseButton()); +} + +#endif // defined(OS_CHROMEOS) + } // namespace message_center diff --git a/ui/views/controls/slide_out_view.cc b/ui/views/controls/slide_out_view.cc index d3cafec..adf1b11 100644 --- a/ui/views/controls/slide_out_view.cc +++ b/ui/views/controls/slide_out_view.cc @@ -10,8 +10,7 @@ namespace views { -SlideOutView::SlideOutView() - : gesture_scroll_amount_(0.f) { +SlideOutView::SlideOutView() { // If accelerated compositing is not available, this widget tracks the // OnSlideOut event but does not render any visible changes. SetPaintToLayer(true); @@ -22,11 +21,14 @@ SlideOutView::~SlideOutView() { } void SlideOutView::OnGestureEvent(ui::GestureEvent* event) { + const float kScrollRatioForClosingNotification = 0.5f; + if (event->type() == ui::ET_SCROLL_FLING_START) { // The threshold for the fling velocity is computed empirically. // The unit is in pixels/second. const float kFlingThresholdForClose = 800.f; - if (fabsf(event->details().velocity_x()) > kFlingThresholdForClose) { + if (is_slide_out_enabled_ && + fabsf(event->details().velocity_x()) > kFlingThresholdForClose) { SlideOutAndClose(event->details().velocity_x() < 0 ? SLIDE_LEFT : SLIDE_RIGHT); event->StopPropagation(); @@ -40,22 +42,35 @@ void SlideOutView::OnGestureEvent(ui::GestureEvent* event) { return; if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN) { - gesture_scroll_amount_ = 0.f; + gesture_amount_ = 0.f; } else if (event->type() == ui::ET_GESTURE_SCROLL_UPDATE) { // The scroll-update events include the incremental scroll amount. - gesture_scroll_amount_ += event->details().scroll_x(); + gesture_amount_ += event->details().scroll_x(); + + float scroll_amount; + if (is_slide_out_enabled_) { + scroll_amount = gesture_amount_; + layer()->SetOpacity(1.f - std::min(fabsf(scroll_amount) / width(), 1.f)); + } else { + if (gesture_amount_ >= 0) { + scroll_amount = std::min(0.5f * gesture_amount_, + width() * kScrollRatioForClosingNotification); + } else { + scroll_amount = + std::max(0.5f * gesture_amount_, + -1.f * width() * kScrollRatioForClosingNotification); + } + } gfx::Transform transform; - transform.Translate(gesture_scroll_amount_, 0.0); + transform.Translate(scroll_amount, 0.0); layer()->SetTransform(transform); - layer()->SetOpacity( - 1.f - std::min(fabsf(gesture_scroll_amount_) / width(), 1.f)); } else if (event->type() == ui::ET_GESTURE_SCROLL_END) { - const float kScrollRatioForClosingNotification = 0.5f; - float scrolled_ratio = fabsf(gesture_scroll_amount_) / width(); - if (scrolled_ratio >= kScrollRatioForClosingNotification) { - SlideOutAndClose(gesture_scroll_amount_ < 0 ? SLIDE_LEFT : SLIDE_RIGHT); + float scrolled_ratio = fabsf(gesture_amount_) / width(); + if (is_slide_out_enabled_ && + scrolled_ratio >= kScrollRatioForClosingNotification) { + SlideOutAndClose(gesture_amount_ < 0 ? SLIDE_LEFT : SLIDE_RIGHT); event->StopPropagation(); return; } diff --git a/ui/views/controls/slide_out_view.h b/ui/views/controls/slide_out_view.h index 7fdb637..89962f2 100644 --- a/ui/views/controls/slide_out_view.h +++ b/ui/views/controls/slide_out_view.h @@ -19,6 +19,8 @@ class VIEWS_EXPORT SlideOutView : public views::View, SlideOutView(); ~SlideOutView() override; + bool slide_out_enabled() { return is_slide_out_enabled_; } + protected: // Called when user intends to close the View by sliding it out. virtual void OnSlideOut() = 0; @@ -26,6 +28,10 @@ class VIEWS_EXPORT SlideOutView : public views::View, // Overridden from views::View. void OnGestureEvent(ui::GestureEvent* event) override; + void set_slide_out_enabled(bool is_slide_out_enabled) { + is_slide_out_enabled_ = is_slide_out_enabled; + } + private: enum SlideDirection { SLIDE_LEFT, @@ -41,7 +47,8 @@ class VIEWS_EXPORT SlideOutView : public views::View, // Overridden from ImplicitAnimationObserver. void OnImplicitAnimationsCompleted() override; - float gesture_scroll_amount_; + float gesture_amount_ = 0.f; + bool is_slide_out_enabled_ = true; DISALLOW_COPY_AND_ASSIGN(SlideOutView); }; |
