// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/notification_center/ash_message_popup_collection.h"
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include "ash/ime/ime_controller_impl.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/keyboard_util.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shell.h"
#include "ash/system/ime_menu/ime_menu_tray.h"
#include "ash/system/notification_center/message_center_test_util.h"
#include "ash/system/notification_center/message_popup_animation_waiter.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/notification_center/views/ash_notification_expand_button.h"
#include "ash/system/notification_center/views/ash_notification_view.h"
#include "ash/system/phonehub/phone_hub_tray.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/unified/unified_slider_view.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chromeos/ash/components/phonehub/fake_phone_hub_manager.h"
#include "chromeos/ash/components/phonehub/feature_status.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/message_center/views/message_popup_collection.h"
#include "ui/message_center/views/message_popup_view.h"
#include "ui/message_center/views/message_view.h"
#include "ui/message_center/views/notification_control_buttons_view.h"
#include "ui/message_center/views/notification_view_base.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/wm/core/window_util.h"
#include "url/gurl.h"
namespace ash {
namespace {
class TestMessagePopupCollection : public AshMessagePopupCollection {
public:
explicit TestMessagePopupCollection(Shelf* shelf)
: AshMessagePopupCollection(display::Screen::GetScreen(), shelf) {}
TestMessagePopupCollection(const TestMessagePopupCollection&) = delete;
TestMessagePopupCollection& operator=(const TestMessagePopupCollection&) =
delete;
~TestMessagePopupCollection() override = default;
bool popup_shown() const { return popup_shown_; }
protected:
void NotifyPopupAdded(message_center::MessagePopupView* popup) override {
AshMessagePopupCollection::NotifyPopupAdded(popup);
popup_shown_ = true;
notification_id_ = popup->message_view()->notification_id();
}
void NotifyPopupRemoved(const std::string& notification_id) override {
AshMessagePopupCollection::NotifyPopupRemoved(notification_id);
EXPECT_EQ(notification_id_, notification_id);
popup_shown_ = false;
notification_id_.clear();
}
private:
bool popup_shown_ = false;
std::string notification_id_;
};
} // namespace
class AshMessagePopupCollectionTest : public AshTestBase,
public testing::WithParamInterface<
/*IsNotifierCollisionEnabled=*/bool> {
public:
AshMessagePopupCollectionTest() = default;
AshMessagePopupCollectionTest(const AshMessagePopupCollectionTest&) = delete;
AshMessagePopupCollectionTest& operator=(
const AshMessagePopupCollectionTest&) = delete;
~AshMessagePopupCollectionTest() override = default;
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
if (IsNotifierCollisionEnabled()) {
enabled_features.emplace_back(features::kNotifierCollision);
} else {
disabled_features.emplace_back(features::kNotifierCollision);
}
scoped_feature_list_->InitWithFeatures(enabled_features, disabled_features);
base::CommandLine::ForCurrentProcess()->AppendSwitch(
keyboard::switches::kEnableVirtualKeyboard);
AshTestBase::SetUp();
}
// Trigger the timeout so that shelf will show/hide immediately.
bool TriggerShelfAutoHideTimeout() {
auto* layout_manager = GetPrimaryShelf()->shelf_layout_manager();
if (!layout_manager->auto_hide_timer_.IsRunning()) {
return false;
}
layout_manager->auto_hide_timer_.FireNow();
return true;
}
void AnimateUntilIdle() {
auto* animation = GetPrimaryPopupCollection()->animation();
while (animation->is_animating()) {
animation->SetCurrentValue(1.0);
animation->End();
}
}
// TODO(b/305075031) clean up after the flag is removed.
bool IsNotifierCollisionEnabled() const { return GetParam(); }
protected:
enum Position { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, OUTSIDE };
AshMessagePopupCollection* GetPrimaryPopupCollection() {
return GetPrimaryNotificationCenterTray()->popup_collection();
}
void UpdateWorkArea(AshMessagePopupCollection* popup_collection,
const display::Display& display) {
popup_collection->StartObserving(display::Screen::GetScreen(), display);
// Update the layout
popup_collection->UpdateWorkArea();
}
message_center::MessagePopupView* GetLastPopUpAdded() {
return GetPrimaryPopupCollection()->last_pop_up_added_;
}
message_center::MessagePopupView* GetLastPopUpAddedForCollection(
AshMessagePopupCollection* collection) {
return collection->last_pop_up_added_;
}
Position GetPositionInDisplay(const gfx::Point& point) {
const gfx::Rect work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
const gfx::Point center_point = work_area.CenterPoint();
if (work_area.x() > point.x() || work_area.y() > point.y() ||
work_area.right() < point.x() || work_area.bottom() < point.y()) {
return OUTSIDE;
}
if (center_point.x() < point.x()) {
return (center_point.y() < point.y()) ? BOTTOM_RIGHT : TOP_RIGHT;
} else {
return (center_point.y() < point.y()) ? BOTTOM_LEFT : TOP_LEFT;
}
}
gfx::Rect GetWorkArea() { return GetPrimaryPopupCollection()->work_area_; }
std::string AddNotification(bool has_image = false,
const GURL& origin_url = GURL(),
bool has_inline_reply = false) {
std::string id = base::NumberToString(notification_id_++);
message_center::MessageCenter::Get()->AddNotification(
CreateSimpleNotification(id, has_image, origin_url, has_inline_reply));
return id;
}
phonehub::FakePhoneHubManager* phone_hub_manager() {
return &phone_hub_manager_;
}
private:
int notification_id_ = 0;
// Fake phone hub manager to show the phone hub tray. Used to test the popup
// collection when the phone hub bubble is showing.
phonehub::FakePhoneHubManager phone_hub_manager_;
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
AshMessagePopupCollectionTest,
/*IsNotifierCollisionEnabled()=*/testing::Bool());
TEST_P(AshMessagePopupCollectionTest, ShelfAlignment) {
const gfx::Rect popup_size(0, 0, 10, 10);
UpdateDisplay("601x600");
gfx::Point popup_point;
auto* popup_collection = GetPrimaryPopupCollection();
popup_point.set_x(popup_collection->GetPopupOriginX(popup_size));
popup_point.set_y(popup_collection->GetBaseline());
EXPECT_EQ(BOTTOM_RIGHT, GetPositionInDisplay(popup_point));
EXPECT_FALSE(popup_collection->IsTopDown());
EXPECT_FALSE(popup_collection->IsFromLeft());
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kRight);
popup_point.set_x(popup_collection->GetPopupOriginX(popup_size));
popup_point.set_y(popup_collection->GetBaseline());
EXPECT_EQ(BOTTOM_RIGHT, GetPositionInDisplay(popup_point));
EXPECT_FALSE(popup_collection->IsTopDown());
EXPECT_FALSE(popup_collection->IsFromLeft());
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
popup_point.set_x(popup_collection->GetPopupOriginX(popup_size));
popup_point.set_y(popup_collection->GetBaseline());
EXPECT_EQ(BOTTOM_LEFT, GetPositionInDisplay(popup_point));
EXPECT_FALSE(popup_collection->IsTopDown());
EXPECT_TRUE(popup_collection->IsFromLeft());
}
TEST_P(AshMessagePopupCollectionTest, LockScreen) {
const gfx::Rect popup_size(0, 0, 10, 10);
auto* popup_collection = GetPrimaryPopupCollection();
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
gfx::Point popup_point;
popup_point.set_x(popup_collection->GetPopupOriginX(popup_size));
popup_point.set_y(popup_collection->GetBaseline());
EXPECT_EQ(BOTTOM_LEFT, GetPositionInDisplay(popup_point));
EXPECT_FALSE(popup_collection->IsTopDown());
EXPECT_TRUE(popup_collection->IsFromLeft());
BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
popup_point.set_x(popup_collection->GetPopupOriginX(popup_size));
popup_point.set_y(popup_collection->GetBaseline());
EXPECT_EQ(BOTTOM_RIGHT, GetPositionInDisplay(popup_point));
EXPECT_FALSE(popup_collection->IsTopDown());
EXPECT_FALSE(popup_collection->IsFromLeft());
}
TEST_P(AshMessagePopupCollectionTest, AutoHide) {
const gfx::Rect popup_size(0, 0, 10, 10);
UpdateDisplay("601x600");
auto* popup_collection = GetPrimaryPopupCollection();
int origin_x = popup_collection->GetPopupOriginX(popup_size);
int shelf_show_baseline = popup_collection->GetBaseline();
// Create a window, otherwise autohide doesn't work.
std::unique_ptr<views::Widget> widget = CreateTestWidget(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, nullptr,
desks_util::GetActiveDeskContainerId(), gfx::Rect(0, 0, 50, 50));
Shelf* shelf = GetPrimaryShelf();
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
EXPECT_EQ(origin_x, popup_collection->GetPopupOriginX(popup_size));
// The baseline when shelf shows should be less than when it hides.
int shelf_hide_baseline = popup_collection->GetBaseline();
EXPECT_LT(shelf_show_baseline, shelf_hide_baseline);
// Tests that popup baseline changes when shelf shows/hides.
// Move down the mouse to show shelf. Popup should move up.
ui::test::EventGenerator* generator = GetEventGenerator();
gfx::Rect display_bounds =
display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
generator->MoveMouseTo(display_bounds.bottom_center());
ASSERT_TRUE(TriggerShelfAutoHideTimeout());
ASSERT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
EXPECT_EQ(shelf_show_baseline, popup_collection->GetBaseline());
// Move the mouse away to hide shelf. Popup should move down.
generator->MoveMouseTo(0, 0);
ASSERT_TRUE(TriggerShelfAutoHideTimeout());
ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
EXPECT_EQ(shelf_hide_baseline, popup_collection->GetBaseline());
}
TEST_P(AshMessagePopupCollectionTest, DisplayResize) {
const gfx::Rect popup_size(0, 0, 10, 10);
UpdateDisplay("601x600");
auto* popup_collection = GetPrimaryPopupCollection();
int origin_x = popup_collection->GetPopupOriginX(popup_size);
int baseline = popup_collection->GetBaseline();
UpdateDisplay("801x800");
EXPECT_LT(origin_x, popup_collection->GetPopupOriginX(popup_size));
EXPECT_LT(baseline, popup_collection->GetBaseline());
UpdateDisplay("500x400");
EXPECT_GT(origin_x, popup_collection->GetPopupOriginX(popup_size));
EXPECT_GT(baseline, popup_collection->GetBaseline());
}
TEST_P(AshMessagePopupCollectionTest, DockedMode) {
const gfx::Rect popup_size(0, 0, 10, 10);
UpdateDisplay("601x600");
auto* popup_collection = GetPrimaryPopupCollection();
int origin_x = popup_collection->GetPopupOriginX(popup_size);
int baseline = popup_collection->GetBaseline();
// Emulate the docked mode; enter to an extended mode, then invoke
// OnNativeDisplaysChanged() with the info for the secondary display only.
UpdateDisplay("601x600,801x800");
std::vector<display::ManagedDisplayInfo> new_info;
new_info.push_back(display_manager()->GetDisplayInfo(
display_manager()->GetDisplayAt(1u).id()));
display_manager()->OnNativeDisplaysChanged(new_info);
EXPECT_LT(origin_x, popup_collection->GetPopupOriginX(popup_size));
EXPECT_LT(baseline, popup_collection->GetBaseline());
}
TEST_P(AshMessagePopupCollectionTest, Extended) {
UpdateDisplay("601x600,801x800");
display::Display second_display = GetSecondaryDisplay();
Shelf* second_shelf =
Shell::GetRootWindowControllerWithDisplayId(second_display.id())->shelf();
AshMessagePopupCollection for_2nd_display(display::Screen::GetScreen(),
second_shelf);
UpdateWorkArea(&for_2nd_display, second_display);
// Make sure that the popup position on the secondary display is
// positioned correctly.
EXPECT_LT(1300, for_2nd_display.GetPopupOriginX(gfx::Rect(0, 0, 10, 10)));
EXPECT_LT(700, for_2nd_display.GetBaseline());
}
// TODO(b/301625873): Fix notification pop-up dismissal on full-screen activated
// with multiple displays. The unit test is passing but the behavior it is
// testing does not work in production.
TEST_P(AshMessagePopupCollectionTest, DISABLED_MixedFullscreenNone) {
UpdateDisplay("601x600,801x800");
Shelf* shelf1 = GetPrimaryShelf();
TestMessagePopupCollection collection1(shelf1);
UpdateWorkArea(&collection1, GetPrimaryDisplay());
Shelf* shelf2 =
Shell::GetRootWindowControllerWithDisplayId(GetSecondaryDisplay().id())
->shelf();
TestMessagePopupCollection collection2(shelf2);
UpdateWorkArea(&collection2, GetSecondaryDisplay());
// No fullscreens, both receive notification.
std::unique_ptr<views::Widget> widget1 =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
widget1->SetFullscreen(false);
AddNotification();
EXPECT_TRUE(collection1.popup_shown());
EXPECT_TRUE(collection2.popup_shown());
// Set screen 1 to fullscreen, popup closes on screen 1, stays on screen 2.
widget1->SetFullscreen(true);
EXPECT_FALSE(collection1.popup_shown());
EXPECT_TRUE(collection2.popup_shown());
}
// TODO(b/301625873): Fix notification pop-up dismissal on full-screen activated
// with multiple displays. The unit test is passing but the behavior it is
// testing does not work in production.
TEST_P(AshMessagePopupCollectionTest, DISABLED_MixedFullscreenSome) {
UpdateDisplay("601x600,801x800");
Shelf* shelf1 = GetPrimaryShelf();
TestMessagePopupCollection collection1(shelf1);
UpdateWorkArea(&collection1, GetPrimaryDisplay());
Shelf* shelf2 =
Shell::GetRootWindowControllerWithDisplayId(GetSecondaryDisplay().id())
->shelf();
TestMessagePopupCollection collection2(shelf2);
UpdateWorkArea(&collection2, GetSecondaryDisplay());
// One fullscreen, non-fullscreen receives notification.
std::unique_ptr<views::Widget> widget =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
widget->SetFullscreen(true);
AddNotification();
EXPECT_FALSE(collection1.popup_shown());
EXPECT_TRUE(collection2.popup_shown());
// Fullscreen toggles, notification now on both.
widget->SetFullscreen(false);
EXPECT_TRUE(collection1.popup_shown());
EXPECT_TRUE(collection2.popup_shown());
}
// TODO(b/301625873): Fix notification pop-up dismissal on full-screen activated
// with multiple displays. The unit test is passing but the behavior it is
// testing does not work in production.
TEST_P(AshMessagePopupCollectionTest, DISABLED_MixedFullscreenAll) {
UpdateDisplay("601x600,801x800");
Shelf* shelf1 = GetPrimaryShelf();
TestMessagePopupCollection collection1(shelf1);
UpdateWorkArea(&collection1, GetPrimaryDisplay());
Shelf* shelf2 =
Shell::GetRootWindowControllerWithDisplayId(GetSecondaryDisplay().id())
->shelf();
TestMessagePopupCollection collection2(shelf2);
UpdateWorkArea(&collection2, GetSecondaryDisplay());
std::unique_ptr<views::Widget> widget1 =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
std::unique_ptr<views::Widget> widget2 = CreateTestWidget(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, nullptr,
desks_util::GetActiveDeskContainerId(), gfx::Rect(700, 0, 50, 50));
// Both fullscreen, no notifications.
widget1->SetFullscreen(true);
widget2->SetFullscreen(true);
AddNotification();
EXPECT_FALSE(collection1.popup_shown());
EXPECT_FALSE(collection2.popup_shown());
// Toggle 1, then the other.
widget1->SetFullscreen(false);
EXPECT_TRUE(collection1.popup_shown());
EXPECT_FALSE(collection2.popup_shown());
widget2->SetFullscreen(false);
EXPECT_TRUE(collection1.popup_shown());
EXPECT_TRUE(collection2.popup_shown());
}
TEST_P(AshMessagePopupCollectionTest, PopupCollectionOriginX) {
display_manager()->SetUnifiedDesktopEnabled(true);
UpdateDisplay("601x600,801x800");
EXPECT_GT(600, GetPrimaryPopupCollection()->GetPopupOriginX(
gfx::Rect(0, 0, 10, 10)));
}
// Tests that when the keyboard is showing that notifications appear above it,
// and that they return to normal once the keyboard is gone.
TEST_P(AshMessagePopupCollectionTest, KeyboardShowing) {
ASSERT_TRUE(keyboard::IsKeyboardEnabled());
ASSERT_TRUE(
keyboard::KeyboardUIController::Get()->IsKeyboardOverscrollEnabled());
UpdateDisplay("601x600");
auto* popup_collection = GetPrimaryPopupCollection();
int baseline = popup_collection->GetBaseline();
Shelf* shelf = GetPrimaryShelf();
gfx::Rect keyboard_bounds(0, 300, 601, 300);
shelf->SetVirtualKeyboardBoundsForTesting(keyboard_bounds);
int keyboard_baseline = popup_collection->GetBaseline();
EXPECT_NE(baseline, keyboard_baseline);
EXPECT_GT(keyboard_bounds.y(), keyboard_baseline);
shelf->SetVirtualKeyboardBoundsForTesting(gfx::Rect());
EXPECT_EQ(baseline, popup_collection->GetBaseline());
}
// Tests that notification bubble baseline is correct when entering and exiting
// overview with a full screen window.
TEST_P(AshMessagePopupCollectionTest, BaselineInOverview) {
UpdateDisplay("800x600");
ASSERT_TRUE(GetPrimaryShelf()->IsHorizontalAlignment());
ASSERT_EQ(SHELF_VISIBLE, GetPrimaryShelf()->GetVisibilityState());
auto* popup_collection = GetPrimaryPopupCollection();
const int baseline_with_visible_shelf = popup_collection->GetBaseline();
std::unique_ptr<views::Widget> widget =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
widget->SetFullscreen(true);
ASSERT_EQ(SHELF_HIDDEN, GetPrimaryShelf()->GetVisibilityState());
const int baseline_with_hidden_shelf = popup_collection->GetBaseline();
EXPECT_NE(baseline_with_visible_shelf, baseline_with_hidden_shelf);
auto* overview_controller = OverviewController::Get();
EnterOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
const int baseline_in_overview = popup_collection->GetBaseline();
EXPECT_EQ(baseline_in_overview, baseline_with_visible_shelf);
ExitOverview();
EXPECT_FALSE(overview_controller->InOverviewSession());
const int baseline_no_overview = popup_collection->GetBaseline();
EXPECT_EQ(baseline_no_overview, baseline_with_hidden_shelf);
}
class NotificationDestructingNotificationDelegate
: public message_center::NotificationDelegate {
public:
NotificationDestructingNotificationDelegate() = default;
NotificationDestructingNotificationDelegate(
const NotificationDestructingNotificationDelegate&) = delete;
NotificationDestructingNotificationDelegate& operator=(
const NotificationDestructingNotificationDelegate&) = delete;
private:
~NotificationDestructingNotificationDelegate() override = default;
// NotificationObserver:
void Click(const std::optional<int>& button_index,
const std::optional<std::u16string>& reply) override {
// Show the UnifiedSystemTrayBubble, which will force all popups to be
// destroyed.
Shell::Get()
->GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->notification_center_tray()
->ShowBubble();
}
};
// Regression test for crbug/1316656. Tests that pressing a button resulting in
// the notification popup getting destroyed does not crash.
TEST_P(AshMessagePopupCollectionTest, PopupDestroyedDuringClick) {
// Create a Notification popup with 1 action button.
message_center::RichNotificationData notification_data;
std::u16string button_text = u"BUTTON_TEXT";
notification_data.buttons.emplace_back(button_text);
auto to_be_destroyed_notification(
std::make_unique<message_center::Notification>(
message_center::NOTIFICATION_TYPE_SIMPLE, "id1",
u"Test Web Notification", u"Notification message body.",
ui::ImageModel(), u"www.test.org", GURL(),
message_center::NotifierId(), notification_data,
new NotificationDestructingNotificationDelegate()));
message_center::MessageCenter::Get()->AddNotification(
std::move(to_be_destroyed_notification));
EXPECT_TRUE(GetLastPopUpAdded());
// Get the view for the button added earlier.
auto* message_view = GetLastPopUpAdded()->message_view();
auto* action_button =
message_view
->GetViewByID(
message_center::NotificationViewBase::ViewId::kActionButtonsRow)
->children()[0]
.get();
EXPECT_EQ(static_cast<views::LabelButton*>(action_button)->GetText(),
button_text);
// Click the action button.
// `NotificationDestructingNotificationDelegate::Click()` will destroy the
// popup during `NotificationViewBase::ActionButtonPressed()`. There should be
// no crash.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
action_button->GetBoundsInScreen().CenterPoint());
event_generator->ClickLeftButton();
// Wait for animation to end.
MessagePopupAnimationWaiter(GetPrimaryPopupCollection()).Wait();
EXPECT_FALSE(GetLastPopUpAdded());
}
// Tests that notification popup baseline is correct when entering and exiting
// tablet mode in a full screen window.
TEST_P(AshMessagePopupCollectionTest, BaselineUpdates_InTabletMode) {
UpdateDisplay("800x600");
ASSERT_TRUE(GetPrimaryShelf()->IsHorizontalAlignment());
auto* popup_collection = GetPrimaryPopupCollection();
// Baseline is higher than the top of the shelf in clamshell mode.
EXPECT_GT(GetPrimaryShelf()->GetShelfBoundsInScreen().y(),
popup_collection->GetBaseline());
auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
// Baseline is higher than the top of the shelf after entering tablet mode.
tablet_mode_controller->SetEnabledForTest(true);
EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
EXPECT_GT(GetPrimaryShelf()->GetShelfBoundsInScreen().y(),
popup_collection->GetBaseline());
// Baseline is higher than the top of the shelf after exiting tablet mode.
tablet_mode_controller->SetEnabledForTest(false);
EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
EXPECT_GT(GetPrimaryShelf()->GetShelfBoundsInScreen().y(),
popup_collection->GetBaseline());
}
TEST_P(AshMessagePopupCollectionTest, BaselineUpdates_InAppMode) {
UpdateDisplay("800x600");
ASSERT_TRUE(GetPrimaryShelf()->IsHorizontalAlignment());
auto* popup_collection = GetPrimaryPopupCollection();
// Enable tablet mode without an open window.
auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
tablet_mode_controller->SetEnabledForTest(true);
EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
auto previous_popup_collection_bottom =
popup_collection->popup_collection_bounds().bottom();
EXPECT_EQ(ShelfBackgroundType::kHomeLauncher,
GetPrimaryShelf()->shelf_layout_manager()->shelf_background_type());
// Enter app mode by showing a window, pop-up collection bottom should update
// its bounds to be lower down the screen than before.
std::unique_ptr<aura::Window> window(
AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400)));
EXPECT_GT(popup_collection->popup_collection_bounds().bottom(),
previous_popup_collection_bottom);
EXPECT_EQ(ShelfBackgroundType::kInApp,
GetPrimaryShelf()->shelf_layout_manager()->shelf_background_type());
// Exit app mode by hiding the window, popup collection bottom should return
// to its previous value.
window->Hide();
EXPECT_EQ(popup_collection->popup_collection_bounds().bottom(),
previous_popup_collection_bottom);
EXPECT_EQ(ShelfBackgroundType::kHomeLauncher,
GetPrimaryShelf()->shelf_layout_manager()->shelf_background_type());
}
TEST_P(AshMessagePopupCollectionTest, BaselineUpdates_OnSliderShown) {
auto* popup_collection = GetPrimaryPopupCollection();
auto* system_tray = GetPrimaryUnifiedSystemTray();
int previous_baseline = popup_collection->GetBaseline();
// Add a notification.
AddNotification();
auto* popup = GetLastPopUpAdded();
ASSERT_TRUE(popup);
// Show a volume slider bubble, baseline should be updated if notifier
// collision is enabled.
system_tray->ShowVolumeSliderBubble();
auto* slider_view = system_tray->GetSliderView();
ASSERT_TRUE(slider_view);
if (IsNotifierCollisionEnabled()) {
// The added popup should appear on top of the slider bubble, separated by
// a padding of `kMarginBetweenPopups`.
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
slider_view->GetBoundsInScreen().y());
EXPECT_EQ(slider_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if notifier collision is disabled.
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
}
// Baseline returns to previous value when the slider bubble is closed.
system_tray->CloseSecondaryBubbles();
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
TEST_P(AshMessagePopupCollectionTest,
BaselineUpdates_OnSliderShownWithAutoHideShelf) {
// Create a window, otherwise autohide doesn't work.
Shelf* shelf = GetPrimaryShelf();
std::unique_ptr<views::Widget> widget = CreateTestWidget(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, nullptr,
desks_util::GetActiveDeskContainerId(), gfx::Rect(0, 0, 50, 50));
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
AddNotification();
auto* popup = GetLastPopUpAdded();
ASSERT_TRUE(popup);
auto* system_tray = GetPrimaryUnifiedSystemTray();
system_tray->ShowVolumeSliderBubble();
auto* slider_view = system_tray->GetSliderView();
ASSERT_TRUE(slider_view);
int shelf_hide_popup_bottom = popup->GetBoundsInScreen().bottom();
if (IsNotifierCollisionEnabled()) {
// On hidden shelf, the added popup should appear on top of the slider
// bubble, separated by a padding of `kMarginBetweenPopups`.
EXPECT_EQ(shelf_hide_popup_bottom + message_center::kMarginBetweenPopups,
slider_view->GetBoundsInScreen().y());
}
// Move mouse to the shelf to make it show.
ui::test::EventGenerator* generator = GetEventGenerator();
gfx::Rect display_bounds =
display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
generator->MoveMouseTo(display_bounds.bottom_center());
ASSERT_TRUE(TriggerShelfAutoHideTimeout());
ASSERT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
// Popup should move up when shelf is shown.
int shelf_show_popup_bottom = popup->GetBoundsInScreen().bottom();
EXPECT_GT(shelf_hide_popup_bottom, shelf_show_popup_bottom);
if (IsNotifierCollisionEnabled()) {
// Should still be on top of slider view.
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
slider_view->GetBoundsInScreen().y());
}
// Move the mouse away to hide the shelf. The shelf should hide now and the
// popup is adjusted correctly.
generator->MoveMouseTo(0, 0);
ASSERT_TRUE(TriggerShelfAutoHideTimeout());
ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
// Popup should move down and still on top of slider view.
EXPECT_EQ(shelf_hide_popup_bottom, popup->GetBoundsInScreen().bottom());
if (IsNotifierCollisionEnabled()) {
// Should still be on top of slider view.
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
slider_view->GetBoundsInScreen().y());
}
}
// Tests that `TrayBubbleView` elements (e.g. Quick Settings) and popups
// are placed on top of each other based on which was shown most recently.
TEST_P(AshMessagePopupCollectionTest, PopupsAndTrayBubbleViewsZOrdering) {
// Add a notification popup.
AddNotification();
auto* popup = GetLastPopUpAdded();
EXPECT_TRUE(popup);
// Opening Quick Settings makes its bubble show in front of the previously
// shown notification pop-up.
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
auto* bubble_native_view =
unified_system_tray->bubble()->GetBubbleWidget()->GetNativeView();
EXPECT_FALSE(popup->GetWidget()->IsStackedAbove(bubble_native_view));
// Adding another popup moves Quick Settings to the back, bringing all popups
// to the top level, showing them in front of the Quick Settings bubble.
AddNotification();
// Wait until the notification popup shows.
MessagePopupAnimationWaiter(GetPrimaryPopupCollection()).Wait();
EXPECT_TRUE(popup->GetWidget()->IsStackedAbove(bubble_native_view));
}
TEST_P(AshMessagePopupCollectionTest, BaselineUpdates_OnTrayBubbleShown) {
auto* popup_collection = GetPrimaryPopupCollection();
int previous_baseline = popup_collection->GetBaseline();
// Show a corner anchored shelf pod bubble. Baseline should update if notifier
// collision is enabled.
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
// Attempt showing a notification when Quick Settings is open.
AddNotification();
auto* popup = GetLastPopUpAdded();
ASSERT_TRUE(popup);
auto* bubble_widget = unified_system_tray->bubble()->GetBubbleWidget();
auto* bubble_view = unified_system_tray->bubble()->GetBubbleView();
if (IsNotifierCollisionEnabled()) {
// The added popup should appear on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups`.
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
EXPECT_EQ(bubble_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// Change the bubble height.
auto bubble_bounds = bubble_widget->GetWindowBoundsInScreen();
bubble_widget->SetBounds(gfx::Rect(bubble_bounds.x(), bubble_bounds.y() + 20,
bubble_bounds.width(),
bubble_bounds.height() - 20));
if (IsNotifierCollisionEnabled()) {
// The baseline for the popup should be adjusted based on the new bubble
// height.
EXPECT_EQ(bubble_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// When bubble is closed, no offset should be set.
// NOTE: We use `CloseNow()` here instead of calling `CloseBubble()` on
// `unified_system_tray` to avoid the delay in the message loop happen in
// `Widget::Close()`.
bubble_widget->CloseNow();
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
// The popup is adjusted to be at the baseline without the offset.
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
TEST_P(AshMessagePopupCollectionTest,
BaselineUpdates_OnTrayBubbleShownWithAutoHideShelf) {
// Create a window, otherwise autohide doesn't work.
Shelf* shelf = GetPrimaryShelf();
std::unique_ptr<views::Widget> widget = CreateTestWidget(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, nullptr,
desks_util::GetActiveDeskContainerId(), gfx::Rect(0, 0, 50, 50));
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
auto* popup_collection = GetPrimaryPopupCollection();
int previous_baseline = popup_collection->GetBaseline();
// Move mouse to the shelf to make it show.
ui::test::EventGenerator* generator = GetEventGenerator();
gfx::Rect display_bounds =
display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
generator->MoveMouseTo(display_bounds.bottom_center());
ASSERT_TRUE(TriggerShelfAutoHideTimeout());
ASSERT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
// Test showing a bubble with the shelf showing in auto-hide state.
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
AddNotification();
auto* popup = GetLastPopUpAdded();
ASSERT_TRUE(popup);
if (IsNotifierCollisionEnabled()) {
// The added popup should appears on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups`.
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
}
int old_popup_bottom = popup->GetBoundsInScreen().bottom();
// Click on the screen corner to hide the shelf and the bubble. The shelf
// should hide now and the popup is adjusted correctly to the baseline.
generator->MoveMouseTo(0, 0);
generator->ClickLeftButton();
base::RunLoop().RunUntilIdle();
ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
// The popup is moved down to be at the baseline without the offset.
EXPECT_LT(old_popup_bottom, popup->GetBoundsInScreen().bottom());
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// Tests that the baseline will not be adjusted when a tray bubble that is not
// anchored to the shelf corner opens (i.e. the IME tray bubble).
TEST_P(AshMessagePopupCollectionTest,
BaselineDoesNotUpdate_OnNonAnchoredTrayBubbleShown) {
Shell::Get()->ime_controller()->ShowImeMenuOnShelf(true);
auto* ime_tray =
StatusAreaWidgetTestHelper::GetStatusAreaWidget()->ime_menu_tray();
ASSERT_TRUE(ime_tray->GetVisible());
auto* popup_collection = GetPrimaryPopupCollection();
int previous_baseline = popup_collection->GetBaseline();
ime_tray->ShowBubble();
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
ime_tray->GetBubbleWidget()->CloseNow();
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
}
TEST_P(AshMessagePopupCollectionTest,
BaselineUpdates_OnTrayBubbleShownWithMultiDisplay) {
UpdateDisplay("801x800,801x800");
display::Display second_display = GetSecondaryDisplay();
Shelf* second_shelf =
Shell::GetRootWindowControllerWithDisplayId(second_display.id())->shelf();
AshMessagePopupCollection secondary_popup_collection(
display::Screen::GetScreen(), second_shelf);
UpdateWorkArea(&secondary_popup_collection, second_display);
auto* primary_popup_collection = GetPrimaryPopupCollection();
int previous_primary_baseline = primary_popup_collection->GetBaseline();
int previous_secondary_baseline = secondary_popup_collection.GetBaseline();
// Add a notification popup.
AddNotification();
auto* primary_popup = GetLastPopUpAdded();
auto* secondary_popup =
GetLastPopUpAddedForCollection(&secondary_popup_collection);
EXPECT_TRUE(primary_popup);
EXPECT_TRUE(secondary_popup);
// Open primary system tray bubble.
auto* primary_system_tray = GetPrimaryUnifiedSystemTray();
LeftClickOn(primary_system_tray);
if (IsNotifierCollisionEnabled()) {
// The primary popup collection should update the baseline and the secondary
// one should reset.
auto* primary_bubble_view = primary_system_tray->bubble()->GetBubbleView();
EXPECT_EQ(
primary_bubble_view->height() + message_center::kMarginBetweenPopups,
previous_primary_baseline - primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
primary_system_tray->GetBubbleBoundsInScreen().y());
EXPECT_EQ(previous_secondary_baseline,
secondary_popup_collection.GetBaseline());
EXPECT_EQ(secondary_popup->GetBoundsInScreen().bottom(),
secondary_popup_collection.GetBaseline());
} else {
// The popup on both display should stay the same if the feature is
// disabled.
EXPECT_EQ(previous_primary_baseline,
primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom(),
primary_popup_collection->GetBaseline());
EXPECT_EQ(previous_secondary_baseline,
secondary_popup_collection.GetBaseline());
EXPECT_EQ(secondary_popup->GetBoundsInScreen().bottom(),
secondary_popup_collection.GetBaseline());
}
// Open secondary system tray bubble.
auto* secondary_system_tray =
second_shelf->GetStatusAreaWidget()->unified_system_tray();
LeftClickOn(secondary_system_tray);
if (IsNotifierCollisionEnabled()) {
// The secondary popup collection should update the baseline and the primary
// one should reset.
EXPECT_EQ(previous_primary_baseline,
primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom(),
primary_popup_collection->GetBaseline());
auto* secondary_bubble_view =
secondary_system_tray->bubble()->GetBubbleView();
EXPECT_EQ(
secondary_bubble_view->height() + message_center::kMarginBetweenPopups,
previous_secondary_baseline - secondary_popup_collection.GetBaseline());
EXPECT_EQ(secondary_popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
secondary_system_tray->GetBubbleBoundsInScreen().y());
} else {
// The popup on both display should stay the same if the feature is
// disabled.
EXPECT_EQ(previous_primary_baseline,
primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom(),
primary_popup_collection->GetBaseline());
EXPECT_EQ(previous_secondary_baseline,
secondary_popup_collection.GetBaseline());
EXPECT_EQ(secondary_popup->GetBoundsInScreen().bottom(),
secondary_popup_collection.GetBaseline());
}
}
TEST_P(AshMessagePopupCollectionTest, HistogramRecordedForShelfPodBubble) {
using SurfaceType = AshMessagePopupCollection::NotifierCollisionSurfaceType;
base::HistogramTester histogram_tester;
const std::string popup_count_histogram_name =
"Ash.NotificationPopup.OnTopOfSurfacesPopupCount";
const std::string surface_type_histogram_name =
"Ash.NotificationPopup.OnTopOfSurfacesType";
AddNotification();
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
if (IsNotifierCollisionEnabled()) {
// The popup should appear on top of the bubble and histogram is recorded.
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 1, 1);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kShelfPodBubble, 1);
} else {
// The popup stays the same if the feature is disabled.
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 1, 0);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kShelfPodBubble, 0);
}
// Add another notification. Histogram should also be recorded with the
// correct bucket for 2 notifications.
AddNotification();
AnimateUntilIdle();
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 2,
IsNotifierCollisionEnabled() ? 1 : 0);
// Close and re-open the bubble. Histogram should be recorded again.
auto* bubble_widget = unified_system_tray->bubble()->GetBubbleWidget();
bubble_widget->CloseNow();
unified_system_tray->ShowBubble();
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 2,
IsNotifierCollisionEnabled() ? 2 : 0);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kShelfPodBubble,
IsNotifierCollisionEnabled() ? 2 : 0);
}
TEST_P(AshMessagePopupCollectionTest, HistogramRecordedForSliderAndHotseat) {
using SurfaceType = AshMessagePopupCollection::NotifierCollisionSurfaceType;
base::HistogramTester histogram_tester;
const std::string popup_count_histogram_name =
"Ash.NotificationPopup.OnTopOfSurfacesPopupCount";
const std::string surface_type_histogram_name =
"Ash.NotificationPopup.OnTopOfSurfacesType";
AddNotification();
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowVolumeSliderBubble();
if (IsNotifierCollisionEnabled()) {
// The popup should appear on top of the bubble and histogram is recorded.
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 1, 1);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kSliderBubble, 1);
} else {
// The popup stays the same if the feature is disabled.
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 1, 0);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kSliderBubble, 0);
}
TabletModeControllerTestApi().EnterTabletMode();
std::unique_ptr<aura::Window> window =
CreateTestWindow(gfx::Rect(0, 0, 400, 400));
wm::ActivateWindow(window.get());
ASSERT_EQ(HotseatState::kHidden,
GetPrimaryShelf()->shelf_layout_manager()->hotseat_state());
// Dragging up to show the hotseat.
gfx::Rect display_bounds =
display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
const gfx::Point start = display_bounds.bottom_center();
const gfx::Point end = start + gfx::Vector2d(0, -80);
GetEventGenerator()->GestureScrollSequence(
start, end, /*duration=*/base::Milliseconds(100),
/*steps=*/4);
ASSERT_EQ(HotseatState::kExtended,
GetPrimaryShelf()->shelf_layout_manager()->hotseat_state());
// Histogram should be recorded accordingly to the adjusted baseline.
if (IsNotifierCollisionEnabled()) {
histogram_tester.ExpectBucketCount(
surface_type_histogram_name,
SurfaceType::kSliderBubbleAndExtendedHotseat, 1);
} else {
histogram_tester.ExpectBucketCount(
surface_type_histogram_name,
SurfaceType::kSliderBubbleAndExtendedHotseat, 0);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kExtendedHotseat, 1);
}
unified_system_tray->CloseSecondaryBubbles();
if (IsNotifierCollisionEnabled()) {
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kExtendedHotseat, 1);
}
}
TEST_P(AshMessagePopupCollectionTest, HistogramNotRecordedWhenAllPopupsClosed) {
if (!IsNotifierCollisionEnabled()) {
return;
}
using SurfaceType = AshMessagePopupCollection::NotifierCollisionSurfaceType;
base::HistogramTester histogram_tester;
const std::string popup_count_histogram_name =
"Ash.NotificationPopup.OnTopOfSurfacesPopupCount";
const std::string surface_type_histogram_name =
"Ash.NotificationPopup.OnTopOfSurfacesType";
UpdateDisplay("801x800");
AddNotification(/*has_image=*/true);
ASSERT_TRUE(GetLastPopUpAdded());
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
// Histogram is recorded when open the bubble.
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 1, 1);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kShelfPodBubble, 1);
// Increase the bubble height so that there's not enough space to display the
// popups on top of it. Note that this only works with screen height of 800
// (set above), and the test might fail if we change the height of bubble
// width or notification width in the future.
auto* bubble_widget = unified_system_tray->bubble()->GetBubbleWidget();
auto bubble_bounds = bubble_widget->GetWindowBoundsInScreen();
bubble_widget->SetBounds(gfx::Rect(bubble_bounds.x(), bubble_bounds.y() - 100,
bubble_bounds.width(),
bubble_bounds.height() + 100));
ASSERT_FALSE(GetLastPopUpAdded());
// Histogram should not be recorded in this case.
histogram_tester.ExpectBucketCount(popup_count_histogram_name, 1, 1);
histogram_tester.ExpectBucketCount(surface_type_histogram_name,
SurfaceType::kShelfPodBubble, 1);
}
TEST_P(AshMessagePopupCollectionTest, NotificationAddedOnTrayBubbleOpen) {
UpdateDisplay("801x600");
auto* popup_collection = GetPrimaryPopupCollection();
int previous_baseline = popup_collection->GetBaseline();
// Show a tray bubble.
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
auto* bubble_view = unified_system_tray->bubble()->GetBubbleView();
// Add a notification with a tray bubble open.
AddNotification();
auto* popup1 = GetLastPopUpAdded();
if (IsNotifierCollisionEnabled()) {
// The added popup should appears on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups`.
ASSERT_EQ(popup1->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
ASSERT_EQ(bubble_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup1->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// Add more popup so that there's not enough space to display the popup above
// the tray bubble. Note that this only works with screen height of 600 (set
// above), and the test might fail if we change the height of bubble width or
// notification width in the future.
auto id2 = AddNotification();
auto id3 = AddNotification();
AnimateUntilIdle();
// The baseline should still be the same when there's notification added.
if (IsNotifierCollisionEnabled()) {
// The added popup should appears on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups`.
EXPECT_EQ(popup1->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
EXPECT_EQ(bubble_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup1->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// Popup 2 should be right above the first one.
auto* popup2 = popup_collection->GetPopupViewForNotificationID(id2);
ASSERT_TRUE(popup2);
EXPECT_EQ(popup2->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
popup1->GetBoundsInScreen().y());
if (IsNotifierCollisionEnabled()) {
// Popup for the third notification should not be displayed since there's
// not enough space.
EXPECT_FALSE(popup_collection->GetPopupViewForNotificationID(id3));
} else {
// The popup is still displayed if the feature is disabled.
EXPECT_TRUE(popup_collection->GetPopupViewForNotificationID(id3));
}
}
TEST_P(AshMessagePopupCollectionTest, NotificationUpdatedOnTrayBubbleOpen) {
UpdateDisplay("801x600");
auto* popup_collection = GetPrimaryPopupCollection();
int previous_baseline = popup_collection->GetBaseline();
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
auto* bubble_view = unified_system_tray->bubble()->GetBubbleView();
AddNotification();
auto* popup1 = GetLastPopUpAdded();
if (IsNotifierCollisionEnabled()) {
// The added popup should appears on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups`.
ASSERT_EQ(popup1->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
ASSERT_EQ(bubble_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup1->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// Add a second popup, it should be on top of the first one and baseline
// offset should stay the same.
auto id2 = AddNotification();
AnimateUntilIdle();
auto* popup2 = popup_collection->GetPopupViewForNotificationID(id2);
EXPECT_EQ(popup2->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
popup1->GetBoundsInScreen().y());
// Update the notification to have an image now, which increases the height of
// the notification and make it not fit above the tray bubble anymore. In this
// case, all the notifications should move down to make room for the change.
// Note that this only works with screen height of 600 (set above), and the
// test might fail if we change the height of bubble width or notification
// width in the future.
message_center::MessageCenter::Get()->UpdateNotification(
id2, CreateSimpleNotification(id2, /*has_image=*/true));
popup2 = popup_collection->GetPopupViewForNotificationID(id2);
AnimateUntilIdle();
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup1->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
EXPECT_EQ(popup2->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
popup1->GetBoundsInScreen().y());
}
// Tests that a corner anchored shelf pod bubble closes when the popup
// collection height expands and it needs more space for it to be displayed.
TEST_P(AshMessagePopupCollectionTest,
BubbleCloses_OnPopupExpandedUsedAvailableSpace) {
UpdateDisplay("801x800");
AddNotification(/*has_image=*/true);
auto* popup1 = GetLastPopUpAdded();
auto id2 = AddNotification();
AnimateUntilIdle();
auto* popup_collection = GetPrimaryPopupCollection();
auto* popup2 = popup_collection->GetPopupViewForNotificationID(id2);
int previous_baseline = popup_collection->GetBaseline();
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
if (IsNotifierCollisionEnabled()) {
// The added popup should appears on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups`.
ASSERT_EQ(popup1->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup1->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
return;
}
EXPECT_EQ(popup2->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
popup1->GetBoundsInScreen().y());
LeftClickOn(static_cast<AshNotificationView*>(popup1->message_view())
->expand_button_for_test());
// Since the space left on the screen above the bubble is not enough to
// display the popup collection when the popup is expanded, the bubble will be
// closed to make room for it and we move down the baseline. Note that this
// only works with screen height of 800 (set above), and the test might fail
// if we change the height of the bubble or notification in the future.
EXPECT_FALSE(unified_system_tray->bubble());
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
}
// Tests that popups will be closed when a tray bubble visibility or bounds
// change and there is not enough space for the popups to be displayed.
TEST_P(AshMessagePopupCollectionTest,
PopupsClose_OnBubbleHeightChangedUsedAvailableSpace) {
UpdateDisplay("801x800");
AddNotification(/*has_image=*/true);
auto* popup = GetLastPopUpAdded();
ASSERT_TRUE(popup);
auto* popup_collection = GetPrimaryPopupCollection();
int previous_baseline = popup_collection->GetBaseline();
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
auto* bubble_widget = unified_system_tray->bubble()->GetBubbleWidget();
auto* bubble_view = unified_system_tray->bubble()->GetBubbleView();
if (IsNotifierCollisionEnabled()) {
// The added popup should appears on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups`.
ASSERT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
unified_system_tray->GetBubbleBoundsInScreen().y());
ASSERT_EQ(bubble_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// The popup collection height before the bubble height is increased.
int previous_popup_collection_height =
popup_collection->popup_collection_bounds().height();
// Increase the bubble height so that there's not enough space to display the
// popups on top of it. Note that this only works with screen height of 800
// (set above), and the test might fail if we change the height of bubble
// width or notification width in the future.
auto bubble_bounds = bubble_widget->GetWindowBoundsInScreen();
bubble_widget->SetBounds(gfx::Rect(bubble_bounds.x(), bubble_bounds.y() - 100,
bubble_bounds.width(),
bubble_bounds.height() + 100));
if (IsNotifierCollisionEnabled()) {
// Since there was not enough space to display the popup, all popups should
// be closed and will go to the notification center tray.
EXPECT_GT(previous_popup_collection_height,
popup_collection->GetBaseline());
EXPECT_FALSE(GetLastPopUpAdded());
} else {
EXPECT_TRUE(GetLastPopUpAdded());
}
}
// Tests that when a shelf pod bubble other than the main status area bubbles
// (e.g. phone hub) is shown and a slider appears, the popup will be on top of
// the shelf pod bubble, not the slider.
TEST_P(AshMessagePopupCollectionTest,
BaselineUpdates_OnTrayBubbleAndSliderShown) {
UpdateDisplay("1001x900");
auto* popup_collection = GetPrimaryPopupCollection();
int previous_baseline = popup_collection->GetBaseline();
phone_hub_manager()->fake_feature_status_provider()->SetStatus(
phonehub::FeatureStatus::kEnabledAndConnected);
auto* phone_hub_tray =
GetPrimaryShelf()->status_area_widget()->phone_hub_tray();
phone_hub_tray->SetPhoneHubManager(phone_hub_manager());
ASSERT_TRUE(phone_hub_tray->GetVisible());
phone_hub_tray->ShowBubble();
auto* system_tray = GetPrimaryUnifiedSystemTray();
system_tray->ShowVolumeSliderBubble();
auto* slider_view = system_tray->GetSliderView();
ASSERT_TRUE(slider_view);
AddNotification(/*has_image=*/true);
auto* popup = GetLastPopUpAdded();
ASSERT_TRUE(popup);
auto* bubble_view = phone_hub_tray->GetBubbleView();
auto* bubble_widget = phone_hub_tray->GetBubbleWidget();
if (IsNotifierCollisionEnabled()) {
// The added popup should appear on top of the tray bubble, separated by a
// padding of `kMarginBetweenPopups` (not on top of the slider).
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
bubble_view->GetBoundsInScreen().y());
ASSERT_EQ(bubble_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if the feature is disabled.
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
}
// Close the phone hub bubble. Popup should sit above slider.
bubble_widget->CloseNow();
if (IsNotifierCollisionEnabled()) {
EXPECT_EQ(popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
slider_view->GetBoundsInScreen().y());
EXPECT_EQ(slider_view->height() + message_center::kMarginBetweenPopups,
previous_baseline - popup_collection->GetBaseline());
} else {
// The popup stays the same if notifier collision is disabled.
EXPECT_EQ(popup->GetBoundsInScreen().bottom(),
popup_collection->GetBaseline());
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
}
// Close the slider. Popup should go back to original baseline.
system_tray->CloseSecondaryBubbles();
EXPECT_EQ(previous_baseline, popup_collection->GetBaseline());
}
// b/293660273
TEST_P(AshMessagePopupCollectionTest,
BaselineUpdates_OnSliderShownWithMultiDisplay) {
UpdateDisplay("0+0-801x800,0+800-801x800");
display::Display second_display = GetSecondaryDisplay();
AshMessagePopupCollection secondary_popup_collection(
display::Screen::GetScreen(),
Shell::GetRootWindowControllerWithDisplayId(second_display.id())
->shelf());
UpdateWorkArea(&secondary_popup_collection, second_display);
auto* primary_popup_collection = GetPrimaryPopupCollection();
int previous_primary_baseline = primary_popup_collection->GetBaseline();
int previous_secondary_baseline = secondary_popup_collection.GetBaseline();
// Add a notification popup.
AddNotification();
auto* primary_popup = GetLastPopUpAdded();
auto* secondary_popup =
GetLastPopUpAddedForCollection(&secondary_popup_collection);
EXPECT_TRUE(primary_popup);
EXPECT_TRUE(secondary_popup);
// Show a slider on the primary display only.
auto* primary_system_tray = GetPrimaryUnifiedSystemTray();
primary_system_tray->ShowVolumeSliderBubble();
auto* slider_view = primary_system_tray->GetSliderView();
ASSERT_TRUE(slider_view);
if (IsNotifierCollisionEnabled()) {
// Popup on primary display should move up, and popup on secondary display
// stay the same.
EXPECT_EQ(
slider_view->height() + message_center::kMarginBetweenPopups,
previous_primary_baseline - primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
slider_view->GetBoundsInScreen().y());
EXPECT_EQ(previous_secondary_baseline,
secondary_popup_collection.GetBaseline());
EXPECT_EQ(secondary_popup->GetBoundsInScreen().bottom(),
secondary_popup_collection.GetBaseline());
} else {
// The popup on both display should stay the same if the feature is
// disabled.
EXPECT_EQ(previous_primary_baseline,
primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom(),
primary_popup_collection->GetBaseline());
EXPECT_EQ(previous_secondary_baseline,
secondary_popup_collection.GetBaseline());
EXPECT_EQ(secondary_popup->GetBoundsInScreen().bottom(),
secondary_popup_collection.GetBaseline());
}
// Show a slider on the secondary display.
auto* secondary_system_tray =
StatusAreaWidgetTestHelper::GetSecondaryStatusAreaWidget()
->unified_system_tray();
secondary_system_tray->ShowVolumeSliderBubble();
auto* secondary_slider_view = secondary_system_tray->GetSliderView();
ASSERT_TRUE(secondary_slider_view);
if (IsNotifierCollisionEnabled()) {
// Popup on both displays should move up since there are sliders on both
// displays.
EXPECT_EQ(
slider_view->height() + message_center::kMarginBetweenPopups,
previous_primary_baseline - primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
slider_view->GetBoundsInScreen().y());
EXPECT_EQ(
secondary_slider_view->height() + message_center::kMarginBetweenPopups,
previous_secondary_baseline - secondary_popup_collection.GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom() +
message_center::kMarginBetweenPopups,
secondary_slider_view->GetBoundsInScreen().y());
} else {
// The popup on both display should stay the same if the feature is
// disabled.
EXPECT_EQ(previous_primary_baseline,
primary_popup_collection->GetBaseline());
EXPECT_EQ(primary_popup->GetBoundsInScreen().bottom(),
primary_popup_collection->GetBaseline());
EXPECT_EQ(previous_secondary_baseline,
secondary_popup_collection.GetBaseline());
EXPECT_EQ(secondary_popup->GetBoundsInScreen().bottom(),
secondary_popup_collection.GetBaseline());
}
}
// b/291988617
TEST_P(AshMessagePopupCollectionTest, QsBubbleNotCloseWhenPopupClose) {
// Skip since b/291988617 only happens when both features are enabled.
if (!IsNotifierCollisionEnabled()) {
return;
}
// Create a window to simulate the step from b/291988617.
std::unique_ptr<views::Widget> widget = CreateTestWidget(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, nullptr,
desks_util::GetActiveDeskContainerId(), gfx::Rect(0, 0, 50, 50));
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
auto id = AddNotification();
auto* popup_collection = GetPrimaryPopupCollection();
auto* popup = popup_collection->GetPopupViewForNotificationID(id);
ASSERT_TRUE(unified_system_tray->bubble());
ASSERT_TRUE(popup);
AnimateUntilIdle();
// Click the notification close button, the popup should disappear. However,
// the bubble show still remain open.
LeftClickOn(static_cast<AshNotificationView*>(popup->message_view())
->control_buttons_view_for_test()
->close_button());
AnimateUntilIdle();
EXPECT_FALSE(popup_collection->GetPopupViewForNotificationID(id));
EXPECT_TRUE(unified_system_tray->bubble());
}
// Same as the above test. But now test with a bubble created by
// `TrayBubbleWrapper` instead of the QS bubble. We will use Phone Hub bubble in
// this case.
TEST_P(AshMessagePopupCollectionTest, BubbleNotCloseWhenPopupClose) {
// Skip since b/291988617 only happens when both features are enabled.
if (!IsNotifierCollisionEnabled()) {
return;
}
// Update display so that notification fit on top of phone hub bubble.
UpdateDisplay("1001x900");
phone_hub_manager()->fake_feature_status_provider()->SetStatus(
phonehub::FeatureStatus::kEnabledAndConnected);
auto* phone_hub_tray =
GetPrimaryShelf()->status_area_widget()->phone_hub_tray();
phone_hub_tray->SetPhoneHubManager(phone_hub_manager());
ASSERT_TRUE(phone_hub_tray->GetVisible());
phone_hub_tray->ShowBubble();
auto id = AddNotification();
auto* popup_collection = GetPrimaryPopupCollection();
auto* popup = popup_collection->GetPopupViewForNotificationID(id);
ASSERT_TRUE(phone_hub_tray->GetBubbleView());
ASSERT_TRUE(popup);
AnimateUntilIdle();
// Click the notification close button, the popup should disappear. However,
// the bubble show still remain open.
LeftClickOn(static_cast<AshNotificationView*>(popup->message_view())
->control_buttons_view_for_test()
->close_button());
AnimateUntilIdle();
EXPECT_FALSE(popup_collection->GetPopupViewForNotificationID(id));
EXPECT_TRUE(phone_hub_tray->GetBubbleView());
}
// For b/346641561
TEST_P(AshMessagePopupCollectionTest, InlineReplyTextfield) {
if (!IsNotifierCollisionEnabled()) {
GTEST_SKIP() << "Popup notifications does not show when notifier collision "
"is enabled";
}
auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
unified_system_tray->ShowBubble();
// Attempt showing a notification when Quick Settings is open.
AddNotification(/*has_image=*/false,
/*origin_url=*/GURL(),
/*has_inline_reply=*/true);
auto* popup = GetLastPopUpAdded();
ASSERT_TRUE(popup);
AnimateUntilIdle();
auto* message_view =
static_cast<AshNotificationView*>(GetLastPopUpAdded()->message_view());
ASSERT_TRUE(message_view);
LeftClickOn(message_view->GetActionButtonsForTest().front());
auto* textfield = message_view->GetInlineReplyForTest()->textfield();
EXPECT_TRUE(textfield->GetVisible());
EXPECT_TRUE(textfield->HasFocus());
PressAndReleaseKey(ui::VKEY_A, ui::EF_NONE);
PressAndReleaseKey(ui::VKEY_A, ui::EF_NONE);
// Make sure that inline reply textfield can receive keyboard events.
EXPECT_EQ(u"aa", textfield->GetText());
}
class AshMessagePopupCollectionMockTimeTest : public ash::AshTestBase {
public:
AshMessagePopupCollectionMockTimeTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
AshMessagePopupCollectionMockTimeTest(
const AshMessagePopupCollectionMockTimeTest&) = delete;
AshMessagePopupCollectionMockTimeTest& operator=(
const AshMessagePopupCollectionMockTimeTest&) = delete;
~AshMessagePopupCollectionMockTimeTest() override = default;
};
TEST_F(AshMessagePopupCollectionMockTimeTest, PopupTimeouts) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
::features::kNotificationsIgnoreRequireInteraction);
auto* popup_collection =
GetPrimaryNotificationCenterTray()->popup_collection();
auto* message_center = message_center::MessageCenter::Get();
std::string id = "0";
auto notification_priorities = {
message_center::DEFAULT_PRIORITY, message_center::HIGH_PRIORITY,
message_center::MAX_PRIORITY, message_center::SYSTEM_PRIORITY};
// Make sure all notification popups below `SYSTEM_PRIORITY` are dismissed
// after `kAutocloseShortDelaySeconds` seconds. Also, make sure
// `SYSTEM_PRIORITY` notifications are dismissed after
// `kAutocloseCrosHighPriorityDelaySeconds`.
for (auto priority : notification_priorities) {
auto notification = CreateSimpleNotification(id);
notification->set_priority(priority);
if (priority == message_center::SYSTEM_PRIORITY) {
notification->SetSystemPriority();
}
message_center->AddNotification(std::move(notification));
EXPECT_TRUE(popup_collection->GetPopupViewForNotificationID(id));
int timeout = priority == message_center::SYSTEM_PRIORITY
? message_center::kAutocloseCrosHighPriorityDelaySeconds
: message_center::kAutocloseShortDelaySeconds;
task_environment()->FastForwardBy(base::Seconds(timeout - 1));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(popup_collection->GetPopupViewForNotificationID(id));
task_environment()->FastForwardBy(base::Seconds(timeout));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(popup_collection->GetPopupViewForNotificationID(id));
message_center->RemoveNotification(id,
/*by_user=*/false);
}
}
} // namespace ash