blob: 9c935f5b7a36a49d89f7f75e88329ad6aefdf983 [file] [log] [blame]
// Copyright 2014 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/ash_message_popup_collection.h"
#include <memory>
#include <utility>
#include <vector>
#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/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/overview/overview_controller.h"
#include "base/command_line.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.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/views/message_popup_collection.h"
#include "ui/message_center/views/message_popup_view.h"
namespace ash {
namespace {
class TestMessagePopupCollection : public AshMessagePopupCollection {
public:
explicit TestMessagePopupCollection(Shelf* shelf)
: AshMessagePopupCollection(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:
AshMessagePopupCollectionTest() = default;
AshMessagePopupCollectionTest(const AshMessagePopupCollectionTest&) = delete;
AshMessagePopupCollectionTest& operator=(
const AshMessagePopupCollectionTest&) = delete;
~AshMessagePopupCollectionTest() override = default;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
keyboard::switches::kEnableVirtualKeyboard);
AshTestBase::SetUp();
SetPopupCollection(
std::make_unique<AshMessagePopupCollection>(GetPrimaryShelf()));
}
void TearDown() override {
popup_collection_.reset();
AshTestBase::TearDown();
}
protected:
enum Position { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, OUTSIDE };
AshMessagePopupCollection* popup_collection() {
return popup_collection_.get();
}
void UpdateWorkArea(AshMessagePopupCollection* popup_collection,
const display::Display& display) {
popup_collection->StartObserving(display::Screen::GetScreen(), display);
// Update the layout
popup_collection->UpdateWorkArea();
}
void SetPopupCollection(std::unique_ptr<AshMessagePopupCollection> delegate) {
if (!delegate.get()) {
popup_collection_.reset();
return;
}
popup_collection_ = std::move(delegate);
UpdateWorkArea(popup_collection_.get(),
display::Screen::GetScreen()->GetPrimaryDisplay());
}
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 popup_collection_->work_area_; }
std::unique_ptr<message_center::Notification> CreateNotification(
const std::string& id) {
return std::make_unique<message_center::Notification>(
message_center::NOTIFICATION_TYPE_BASE_FORMAT, id, u"test_title",
u"test message", gfx::Image(), std::u16string() /* display_source */,
GURL(), message_center::NotifierId(),
message_center::RichNotificationData(),
new message_center::NotificationDelegate());
}
std::string AddNotification() {
std::string id = base::NumberToString(notification_id_++);
message_center::MessageCenter::Get()->AddNotification(
CreateNotification(id));
return id;
}
private:
int notification_id_ = 0;
std::unique_ptr<AshMessagePopupCollection> popup_collection_;
};
TEST_F(AshMessagePopupCollectionTest, ShelfAlignment) {
const gfx::Rect toast_size(0, 0, 10, 10);
UpdateDisplay("601x600");
gfx::Point toast_point;
toast_point.set_x(popup_collection()->GetToastOriginX(toast_size));
toast_point.set_y(popup_collection()->GetBaseline());
EXPECT_EQ(BOTTOM_RIGHT, GetPositionInDisplay(toast_point));
EXPECT_FALSE(popup_collection()->IsTopDown());
EXPECT_FALSE(popup_collection()->IsFromLeft());
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kRight);
toast_point.set_x(popup_collection()->GetToastOriginX(toast_size));
toast_point.set_y(popup_collection()->GetBaseline());
EXPECT_EQ(BOTTOM_RIGHT, GetPositionInDisplay(toast_point));
EXPECT_FALSE(popup_collection()->IsTopDown());
EXPECT_FALSE(popup_collection()->IsFromLeft());
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
toast_point.set_x(popup_collection()->GetToastOriginX(toast_size));
toast_point.set_y(popup_collection()->GetBaseline());
EXPECT_EQ(BOTTOM_LEFT, GetPositionInDisplay(toast_point));
EXPECT_FALSE(popup_collection()->IsTopDown());
EXPECT_TRUE(popup_collection()->IsFromLeft());
}
TEST_F(AshMessagePopupCollectionTest, LockScreen) {
const gfx::Rect toast_size(0, 0, 10, 10);
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
gfx::Point toast_point;
toast_point.set_x(popup_collection()->GetToastOriginX(toast_size));
toast_point.set_y(popup_collection()->GetBaseline());
EXPECT_EQ(BOTTOM_LEFT, GetPositionInDisplay(toast_point));
EXPECT_FALSE(popup_collection()->IsTopDown());
EXPECT_TRUE(popup_collection()->IsFromLeft());
BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
toast_point.set_x(popup_collection()->GetToastOriginX(toast_size));
toast_point.set_y(popup_collection()->GetBaseline());
EXPECT_EQ(BOTTOM_RIGHT, GetPositionInDisplay(toast_point));
EXPECT_FALSE(popup_collection()->IsTopDown());
EXPECT_FALSE(popup_collection()->IsFromLeft());
}
TEST_F(AshMessagePopupCollectionTest, AutoHide) {
const gfx::Rect toast_size(0, 0, 10, 10);
UpdateDisplay("601x600");
int origin_x = popup_collection()->GetToastOriginX(toast_size);
int baseline = popup_collection()->GetBaseline();
// Create a window, otherwise autohide doesn't work.
std::unique_ptr<views::Widget> widget = CreateTestWidget(
nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect(0, 0, 50, 50));
Shelf* shelf = GetPrimaryShelf();
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
EXPECT_EQ(origin_x, popup_collection()->GetToastOriginX(toast_size));
EXPECT_LT(baseline, popup_collection()->GetBaseline());
}
TEST_F(AshMessagePopupCollectionTest, DisplayResize) {
const gfx::Rect toast_size(0, 0, 10, 10);
UpdateDisplay("601x600");
int origin_x = popup_collection()->GetToastOriginX(toast_size);
int baseline = popup_collection()->GetBaseline();
UpdateDisplay("801x800");
EXPECT_LT(origin_x, popup_collection()->GetToastOriginX(toast_size));
EXPECT_LT(baseline, popup_collection()->GetBaseline());
UpdateDisplay("500x400");
EXPECT_GT(origin_x, popup_collection()->GetToastOriginX(toast_size));
EXPECT_GT(baseline, popup_collection()->GetBaseline());
}
TEST_F(AshMessagePopupCollectionTest, DockedMode) {
const gfx::Rect toast_size(0, 0, 10, 10);
UpdateDisplay("601x600");
int origin_x = popup_collection()->GetToastOriginX(toast_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()->GetToastOriginX(toast_size));
EXPECT_LT(baseline, popup_collection()->GetBaseline());
}
TEST_F(AshMessagePopupCollectionTest, TrayHeight) {
const gfx::Rect toast_size(0, 0, 10, 10);
UpdateDisplay("601x600");
int origin_x = popup_collection()->GetToastOriginX(toast_size);
int baseline = popup_collection()->GetBaseline();
// Simulate the system tray bubble being open.
const int kTrayHeight = 100;
popup_collection()->SetTrayBubbleHeight(kTrayHeight);
EXPECT_EQ(origin_x, popup_collection()->GetToastOriginX(toast_size));
EXPECT_EQ(baseline - kTrayHeight - message_center::kMarginBetweenPopups,
popup_collection()->GetBaseline());
}
TEST_F(AshMessagePopupCollectionTest, Extended) {
UpdateDisplay("601x600,801x800");
SetPopupCollection(
std::make_unique<AshMessagePopupCollection>(GetPrimaryShelf()));
display::Display second_display = GetSecondaryDisplay();
Shelf* second_shelf =
Shell::GetRootWindowControllerWithDisplayId(second_display.id())->shelf();
AshMessagePopupCollection for_2nd_display(second_shelf);
UpdateWorkArea(&for_2nd_display, second_display);
// Make sure that the toast position on the secondary display is
// positioned correctly.
EXPECT_LT(1300, for_2nd_display.GetToastOriginX(gfx::Rect(0, 0, 10, 10)));
EXPECT_LT(700, for_2nd_display.GetBaseline());
}
TEST_F(AshMessagePopupCollectionTest, 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();
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());
}
TEST_F(AshMessagePopupCollectionTest, 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();
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());
}
TEST_F(AshMessagePopupCollectionTest, 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();
std::unique_ptr<views::Widget> widget2 =
CreateTestWidget(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_F(AshMessagePopupCollectionTest, Unified) {
display_manager()->SetUnifiedDesktopEnabled(true);
// Reset the delegate as the primary display's shelf will be destroyed during
// transition.
SetPopupCollection(nullptr);
UpdateDisplay("601x600,801x800");
SetPopupCollection(
std::make_unique<AshMessagePopupCollection>(GetPrimaryShelf()));
EXPECT_GT(600, popup_collection()->GetToastOriginX(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_F(AshMessagePopupCollectionTest, KeyboardShowing) {
ASSERT_TRUE(keyboard::IsKeyboardEnabled());
ASSERT_TRUE(
keyboard::KeyboardUIController::Get()->IsKeyboardOverscrollEnabled());
UpdateDisplay("601x600");
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_F(AshMessagePopupCollectionTest, BaselineInOverview) {
UpdateDisplay("800x600");
ASSERT_TRUE(GetPrimaryShelf()->IsHorizontalAlignment());
ASSERT_EQ(SHELF_VISIBLE, GetPrimaryShelf()->GetVisibilityState());
const int baseline_with_visible_shelf = popup_collection()->GetBaseline();
std::unique_ptr<views::Widget> widget = CreateTestWidget();
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 = Shell::Get()->overview_controller();
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);
}
} // namespace ash