| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/message_center/unified_message_center_bubble.h" |
| |
| #include <memory> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/system/message_center/unified_message_center_view.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/unified/unified_system_tray.h" |
| #include "ash/system/unified/unified_system_tray_bubble.h" |
| #include "ash/system/unified/unified_system_tray_controller.h" |
| #include "ash/system/unified/unified_system_tray_view.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/prefs/pref_service.h" |
| #include "ui/events/keycodes/keyboard_codes_posix.h" |
| #include "ui/message_center/message_center.h" |
| |
| using message_center::MessageCenter; |
| using message_center::Notification; |
| |
| #include <iostream> |
| |
| namespace ash { |
| |
| class UnifiedMessageCenterBubbleTest |
| : public AshTestBase, |
| public testing::WithParamInterface<bool> { |
| public: |
| UnifiedMessageCenterBubbleTest() = default; |
| |
| UnifiedMessageCenterBubbleTest(const UnifiedMessageCenterBubbleTest&) = |
| delete; |
| UnifiedMessageCenterBubbleTest& operator=( |
| const UnifiedMessageCenterBubbleTest&) = delete; |
| |
| ~UnifiedMessageCenterBubbleTest() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>(); |
| scoped_feature_list_->InitWithFeatureState(features::kNotificationsRefresh, |
| IsNotificationsRefreshEnabled()); |
| |
| AshTestBase::SetUp(); |
| } |
| |
| bool IsNotificationsRefreshEnabled() const { return GetParam(); } |
| |
| protected: |
| std::string AddWebNotification() { |
| std::string id = base::NumberToString(id_++); |
| MessageCenter::Get()->AddNotification(std::make_unique<Notification>( |
| message_center::NOTIFICATION_TYPE_SIMPLE, id, u"title", u"message", |
| gfx::Image(), std::u16string(), GURL(), |
| message_center::NotifierId(GURL(u"example.com"), u"webpagetitle"), |
| message_center::RichNotificationData(), /*delegate=*/nullptr)); |
| return id; |
| } |
| |
| std::string AddNotification() { |
| std::string id = base::NumberToString(id_++); |
| MessageCenter::Get()->AddNotification(std::make_unique<Notification>( |
| message_center::NOTIFICATION_TYPE_BASE_FORMAT, id, u"test title", |
| u"test message", gfx::Image(), std::u16string(), GURL(), |
| message_center::NotifierId(), message_center::RichNotificationData(), |
| new message_center::NotificationDelegate())); |
| return id; |
| } |
| |
| void RemoveAllNotifications() { |
| message_center::MessageCenter::Get()->RemoveAllNotifications( |
| /*by_user=*/true, MessageCenter::RemoveType::ALL); |
| GetMessageCenterBubble() |
| ->message_center_view() |
| ->message_list_view() |
| ->ResetBounds(); |
| } |
| |
| UnifiedMessageCenterBubble* GetMessageCenterBubble() { |
| return GetPrimaryUnifiedSystemTray()->message_center_bubble(); |
| } |
| |
| UnifiedSystemTrayBubble* GetSystemTrayBubble() { |
| return GetPrimaryUnifiedSystemTray()->bubble(); |
| } |
| |
| int MessageCenterSeparationHeight() { |
| gfx::Rect message_bubble_bounds = |
| GetMessageCenterBubble()->GetBubbleView()->GetBoundsInScreen(); |
| gfx::Rect tray_bounds = |
| GetSystemTrayBubble()->GetBubbleView()->GetBoundsInScreen(); |
| |
| return message_bubble_bounds.y() + message_bubble_bounds.height() - |
| tray_bounds.y(); |
| } |
| |
| bool IsMessageCenterCollapsed() { |
| return GetMessageCenterBubble()->message_center_view()->collapsed(); |
| } |
| |
| bool IsQuickSettingsCollapsed() { |
| return !GetSystemTrayBubble()->controller_for_test()->IsExpanded(); |
| } |
| |
| // Helper functions for focus cycle testing. |
| void DoTab() { |
| PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EventFlags::EF_NONE); |
| } |
| |
| void DoShiftTab() { |
| PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, |
| ui::EventFlags::EF_SHIFT_DOWN); |
| } |
| |
| void DoAltShiftN() { |
| PressAndReleaseKey( |
| ui::KeyboardCode::VKEY_N, |
| ui::EventFlags::EF_SHIFT_DOWN | ui::EventFlags::EF_ALT_DOWN); |
| } |
| |
| void DoEsc() { PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE); } |
| |
| void ToggleExpanded() { |
| GetSystemTrayBubble()->controller_for_test()->ToggleExpanded(); |
| } |
| |
| void WaitForAnimation() { |
| // Some animations do not complete without checking is_animating(); |
| do { |
| base::RunLoop().RunUntilIdle(); |
| } while (GetSystemTrayBubble() && |
| GetSystemTrayBubble()->controller_for_test() && |
| GetSystemTrayBubble()->controller_for_test()->animation_ && |
| GetSystemTrayBubble() |
| ->controller_for_test() |
| ->animation_->is_animating()); |
| } |
| |
| views::View* GetFirstMessageCenterFocusable() { |
| return GetMessageCenterBubble() |
| ->message_center_view() |
| ->GetFirstFocusableChild(); |
| } |
| |
| views::View* GetLastMessageCenterFocusable() { |
| return GetMessageCenterBubble() |
| ->message_center_view() |
| ->GetLastFocusableChild(); |
| } |
| |
| views::View* GetFirstQuickSettingsFocusable() { |
| return GetSystemTrayBubble()->unified_view()->GetFirstFocusableChild(); |
| } |
| |
| views::View* GetLastQuickSettingsFocusable() { |
| return GetSystemTrayBubble()->unified_view()->GetLastFocusableChild(); |
| } |
| |
| private: |
| int id_ = 0; |
| std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| UnifiedMessageCenterBubbleTest, |
| testing::Bool() /* IsNotificationsRefreshEnabled() */); |
| |
| TEST_P(UnifiedMessageCenterBubbleTest, PositionedAboveSystemTray) { |
| const int total_notifications = 5; |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| AddNotification(); |
| |
| const int reference_separation = MessageCenterSeparationHeight(); |
| |
| // The message center should be positioned a constant distance above |
| // the tray as it grows in size. |
| for (int i = 0; i < total_notifications; i++) { |
| AddNotification(); |
| EXPECT_EQ(reference_separation, MessageCenterSeparationHeight()); |
| } |
| |
| // When the system tray is collapsing, message view should stay at a constant |
| // height above it. |
| for (double i = 1.0; i >= 0; i -= 0.1) { |
| GetSystemTrayBubble()->unified_view()->SetExpandedAmount(i); |
| EXPECT_EQ(reference_separation, MessageCenterSeparationHeight()); |
| } |
| |
| // When the system tray is collapsing, message view should stay at a constant |
| // height above it. |
| for (double i = 0.0; i <= 1.0; i += 0.1) { |
| GetSystemTrayBubble()->unified_view()->SetExpandedAmount(i); |
| EXPECT_EQ(reference_separation, MessageCenterSeparationHeight()); |
| } |
| } |
| |
| TEST_P(UnifiedMessageCenterBubbleTest, FocusCycle) { |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| AddNotification(); |
| AddNotification(); |
| |
| views::Widget* quick_settings_widget = |
| GetSystemTrayBubble()->GetBubbleWidget(); |
| views::Widget* message_center_widget = |
| GetMessageCenterBubble()->GetBubbleWidget(); |
| |
| // First tab should focus the first element in the quick settings bubble. |
| DoTab(); |
| EXPECT_TRUE(quick_settings_widget->IsActive()); |
| EXPECT_FALSE(message_center_widget->IsActive()); |
| EXPECT_EQ(quick_settings_widget->GetFocusManager()->GetFocusedView(), |
| GetFirstQuickSettingsFocusable()); |
| |
| // Keep tabbing until we reach the last focusable element in the quick |
| // settings bubble. |
| while (quick_settings_widget->GetFocusManager()->GetFocusedView() != |
| GetLastQuickSettingsFocusable()) { |
| DoTab(); |
| } |
| |
| // Tab at the last element in the quick settings bubble should move focus to |
| // the first element in the message center. |
| DoTab(); |
| EXPECT_TRUE(message_center_widget->IsActive()); |
| EXPECT_FALSE(quick_settings_widget->IsActive()); |
| EXPECT_EQ(message_center_widget->GetFocusManager()->GetFocusedView(), |
| GetFirstMessageCenterFocusable()); |
| |
| // Keep tabbing until we reach the last focusable element in the message |
| // center bubble. |
| while (message_center_widget->GetFocusManager()->GetFocusedView() != |
| GetLastMessageCenterFocusable()) { |
| DoTab(); |
| } |
| |
| // Tab at the last element in the message center bubble should move focus to |
| // the first element in the quick settings bubble. |
| DoTab(); |
| EXPECT_TRUE(quick_settings_widget->IsActive()); |
| EXPECT_FALSE(message_center_widget->IsActive()); |
| EXPECT_EQ(quick_settings_widget->GetFocusManager()->GetFocusedView(), |
| GetFirstQuickSettingsFocusable()); |
| } |
| |
| TEST_P(UnifiedMessageCenterBubbleTest, CollapseState) { |
| AddNotification(); |
| AddNotification(); |
| |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| int small_display_height = |
| GetSystemTrayBubble()->unified_view()->GetCollapsedSystemTrayHeight() + |
| (2 * kMessageCenterCollapseThreshold); |
| int large_display_height = |
| GetSystemTrayBubble()->unified_view()->GetExpandedSystemTrayHeight() + |
| (4 * kMessageCenterCollapseThreshold); |
| GetPrimaryUnifiedSystemTray()->CloseBubble(); |
| |
| // Clear pref to test behavior when expanded pref is not set. |
| PrefService* prefs = |
| Shell::Get()->session_controller()->GetLastActiveUserPrefService(); |
| prefs->ClearPref(prefs::kSystemTrayExpanded); |
| |
| // Message center should open in expanded state when screen height is |
| // limited. |
| UpdateDisplay(base::StringPrintf("1000x%d", small_display_height)); |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| WaitForAnimation(); |
| EXPECT_TRUE(IsQuickSettingsCollapsed()); |
| EXPECT_FALSE(IsMessageCenterCollapsed()); |
| |
| // Message center should be collapsed when quick settings is expanded |
| // with limited screen height. |
| ToggleExpanded(); |
| WaitForAnimation(); |
| EXPECT_TRUE(IsMessageCenterCollapsed()); |
| |
| ToggleExpanded(); |
| WaitForAnimation(); |
| EXPECT_FALSE(IsMessageCenterCollapsed()); |
| |
| GetPrimaryUnifiedSystemTray()->CloseBubble(); |
| |
| UpdateDisplay(base::StringPrintf("1000x%d", large_display_height)); |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| EXPECT_FALSE(IsMessageCenterCollapsed()); |
| |
| ToggleExpanded(); |
| WaitForAnimation(); |
| EXPECT_FALSE(IsMessageCenterCollapsed()); |
| |
| ToggleExpanded(); |
| WaitForAnimation(); |
| EXPECT_FALSE(IsMessageCenterCollapsed()); |
| } |
| |
| TEST_P(UnifiedMessageCenterBubbleTest, FocusCycleWithNoNotifications) { |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| |
| views::Widget* quick_settings_widget = |
| GetSystemTrayBubble()->GetBubbleWidget(); |
| views::Widget* message_center_widget = |
| GetMessageCenterBubble()->GetBubbleWidget(); |
| |
| // First tab should focus the first element in the quick settings bubble. |
| DoTab(); |
| EXPECT_TRUE(quick_settings_widget->IsActive()); |
| EXPECT_FALSE(message_center_widget->IsActive()); |
| EXPECT_EQ(quick_settings_widget->GetFocusManager()->GetFocusedView(), |
| GetFirstQuickSettingsFocusable()); |
| |
| // Keep tabbing until we reach the last focusable element in the quick |
| // settings bubble. |
| while (quick_settings_widget->GetFocusManager()->GetFocusedView() != |
| GetLastQuickSettingsFocusable()) { |
| DoTab(); |
| } |
| |
| // Tab at the last element in the quick settings bubble should move focus to |
| // the first element in the quick settings bubble. |
| DoTab(); |
| EXPECT_TRUE(quick_settings_widget->IsActive()); |
| EXPECT_FALSE(message_center_widget->IsActive()); |
| EXPECT_EQ(quick_settings_widget->GetFocusManager()->GetFocusedView(), |
| GetFirstQuickSettingsFocusable()); |
| } |
| |
| TEST_P(UnifiedMessageCenterBubbleTest, BubbleBounds) { |
| // Set display size where the message center is not collapsed. |
| UpdateDisplay("0+0-1280×1024"); |
| |
| // Ensure message center is not collapsed. |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| ASSERT_FALSE(GetMessageCenterBubble()->IsMessageCenterCollapsed()); |
| |
| // Add enough notifications so that the scroll bar is visible. |
| while (!GetMessageCenterBubble()->message_center_view()->IsScrollBarVisible()) |
| AddNotification(); |
| |
| // The message center bubble should be positioned above the system tray |
| // bubble. |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), |
| GetSystemTrayBubble()->GetBoundsInScreen().y()); |
| GetPrimaryUnifiedSystemTray()->CloseBubble(); |
| |
| // Go into overview mode, check bounds again. |
| EnterOverview(); |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), |
| GetSystemTrayBubble()->GetBoundsInScreen().y()); |
| GetPrimaryUnifiedSystemTray()->CloseBubble(); |
| ExitOverview(); |
| |
| // Go into tablet mode, check bounds again. |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), |
| GetSystemTrayBubble()->GetBoundsInScreen().y()); |
| GetPrimaryUnifiedSystemTray()->CloseBubble(); |
| |
| // Go into overview mode inside tablet mode, check bounds again. |
| EnterOverview(); |
| GetPrimaryUnifiedSystemTray()->ShowBubble(); |
| EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), |
| GetSystemTrayBubble()->GetBoundsInScreen().y()); |
| GetPrimaryUnifiedSystemTray()->CloseBubble(); |
| } |
| |
| TEST_P(UnifiedMessageCenterBubbleTest, HandleAccelerators) { |
| auto id = AddWebNotification(); |
| WaitForAnimation(); |
| |
| // Open and focus message center. |
| DoAltShiftN(); |
| WaitForAnimation(); |
| EXPECT_TRUE(GetMessageCenterBubble()->IsMessageCenterVisible()); |
| EXPECT_EQ( |
| 1u, |
| message_center::MessageCenter::Get()->GetVisibleNotifications().size()); |
| |
| views::Widget* quick_settings_widget = |
| GetSystemTrayBubble()->GetBubbleWidget(); |
| views::Widget* message_center_widget = |
| GetMessageCenterBubble()->GetBubbleWidget(); |
| EXPECT_FALSE(quick_settings_widget->IsActive()); |
| EXPECT_TRUE(message_center_widget->IsActive()); |
| |
| RemoveAllNotifications(); |
| WaitForAnimation(); |
| EXPECT_EQ( |
| 0u, |
| message_center::MessageCenter::Get()->GetVisibleNotifications().size()); |
| EXPECT_FALSE(quick_settings_widget->IsActive()); |
| EXPECT_FALSE(message_center_widget->IsActive()); |
| |
| EXPECT_EQ(nullptr, GetFirstMessageCenterFocusable()); |
| EXPECT_EQ(nullptr, |
| message_center_widget->GetFocusManager()->GetFocusedView()); |
| |
| // Press Esc to close system tray. |
| DoEsc(); |
| WaitForAnimation(); |
| EXPECT_EQ(nullptr, |
| GetPrimaryUnifiedSystemTray()->GetFocusManager()->GetFocusedView()); |
| } |
| |
| } // namespace ash |