#include "ash/display/display_alignment_controller.h"
#include <algorithm>
#include "ash/constants/ash_features.h"
#include "ash/display/display_alignment_indicator.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/timer/mock_timer.h"
#include "ui/display/display_layout_builder.h"
#include "ui/display/manager/display_layout_store.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
enum class EdgeType { kTop, kRight, kBottom, kLeft };
DisplayAlignmentController* display_alignment_controller() {
return Shell::Get()->display_alignment_controller();
}
void TriggerIndicator(const display::Display& display, EdgeType edge) {
WindowTreeHostManager* window_tree_host_manager =
Shell::Get()->window_tree_host_manager();
aura::Window* primary_root =
window_tree_host_manager->GetRootWindowForDisplayId(display.id());
ui::test::EventGenerator primary_generator(primary_root);
const gfx::Rect display_bounds = primary_root->GetBoundsInRootWindow();
gfx::Point point_on_edge;
if (edge == EdgeType::kTop)
point_on_edge = display_bounds.top_center();
else if (edge == EdgeType::kRight)
point_on_edge = display_bounds.right_center();
else if (edge == EdgeType::kBottom)
point_on_edge = display_bounds.bottom_center();
else
point_on_edge = display_bounds.left_center();
primary_generator.MoveMouseToInHost(point_on_edge);
primary_generator.MoveMouseToInHost(display_bounds.CenterPoint());
primary_generator.MoveMouseToInHost(point_on_edge);
}
}
class DisplayAlignmentControllerTest : public AshTestBase {
public:
DisplayAlignmentControllerTest() = default;
~DisplayAlignmentControllerTest() override = default;
protected:
void LockScreen() { GetSessionControllerClient()->LockScreen(); }
void UnlockScreen() { GetSessionControllerClient()->UnlockScreen(); }
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(features::kDisplayAlignAssist);
AshTestBase::SetUp();
std::unique_ptr<base::MockOneShotTimer> mock_timer =
std::make_unique<base::MockOneShotTimer>();
mock_timer_ptr_ = mock_timer.get();
display_alignment_controller()->SetTimerForTesting(std::move(mock_timer));
}
void DragDisplay(int64_t id, int32_t delta_x, int32_t delta_y) {
display_alignment_controller()->DisplayDragged(id, delta_x, delta_y);
}
bool NoIndicatorsExist() {
return display_alignment_controller()
->GetActiveIndicatorsForTesting()
.empty();
}
void CheckIndicatorShown(size_t num_indicators,
const display::Display& src_display) {
const auto& active_indicators =
display_alignment_controller()->GetActiveIndicatorsForTesting();
WindowTreeHostManager* window_tree_host_manager =
Shell::Get()->window_tree_host_manager();
aura::Window* primary_root =
window_tree_host_manager->GetRootWindowForDisplayId(src_display.id());
EXPECT_EQ(num_indicators, active_indicators.size());
for (const auto& indicator : active_indicators) {
ASSERT_TRUE(indicator);
EXPECT_TRUE(indicator->indicator_widget_.IsVisible());
aura::Window* current_root =
indicator->indicator_widget_.GetNativeWindow()->GetRootWindow();
if (current_root == primary_root) {
ASSERT_TRUE(indicator->pill_widget_);
EXPECT_TRUE(indicator->pill_widget_->IsVisible());
} else {
EXPECT_FALSE(indicator->pill_widget_);
}
}
}
void CheckPreviewIndicatorShown(int64_t dragged_display_id,
int64_t target_display_id,
bool is_visible) {
ASSERT_EQ(dragged_display_id,
display_alignment_controller()->GetDraggedDisplayIdForTesting());
const auto& active_indicators_ =
display_alignment_controller()->GetActiveIndicatorsForTesting();
const auto& iter =
std::ranges::find(active_indicators_, target_display_id,
&DisplayAlignmentIndicator::display_id);
if (iter == active_indicators_.end()) {
EXPECT_FALSE(is_visible);
return;
}
DisplayAlignmentIndicator* indicator = iter->get();
ASSERT_TRUE(indicator);
const views::Widget& indicator_widget = indicator->indicator_widget_;
if (!is_visible) {
EXPECT_FALSE(indicator_widget.IsVisible());
return;
}
EXPECT_EQ(Shell::GetRootWindowForDisplayId(target_display_id),
indicator_widget.GetNativeWindow()->GetRootWindow());
EXPECT_TRUE(indicator_widget.IsVisible());
}
raw_ptr<base::MockOneShotTimer, DanglingUntriaged> mock_timer_ptr_ = nullptr;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(DisplayAlignmentControllerTest, SingleDisplayNoIndicators) {
UpdateDisplay("1920x1080");
EXPECT_TRUE(NoIndicatorsExist());
}
TEST_F(DisplayAlignmentControllerTest, TriggerIndicatorPrimary) {
UpdateDisplay("1920x1080,1366x768");
const auto& display = display_manager()->GetDisplayAt(0);
aura::Window* root_window =
Shell::Get()->window_tree_host_manager()->GetRootWindowForDisplayId(
display.id());
ui::test::EventGenerator generator(root_window);
generator.MoveMouseToInHost(gfx::Point(0, 0));
EXPECT_TRUE(NoIndicatorsExist());
generator.MoveMouseToInHost(gfx::Point(20, 50));
EXPECT_TRUE(NoIndicatorsExist());
generator.MoveMouseToInHost(gfx::Point(0, 0));
CheckIndicatorShown(2, display);
mock_timer_ptr_->Fire();
EXPECT_TRUE(NoIndicatorsExist());
}
TEST_F(DisplayAlignmentControllerTest, RetriggerIndicatorPrimary) {
UpdateDisplay("1920x1080,1366x768");
const auto& display = display_manager()->GetDisplayAt(0);
EXPECT_TRUE(NoIndicatorsExist());
TriggerIndicator(display, EdgeType::kTop);
CheckIndicatorShown(2, display);
mock_timer_ptr_->Fire();
EXPECT_TRUE(NoIndicatorsExist());
TriggerIndicator(display, EdgeType::kTop);
CheckIndicatorShown(2, display);
}
TEST_F(DisplayAlignmentControllerTest, OnlyTriggerNeighboringIndicators) {
UpdateDisplay("1920x1080,1366x768,800x600");
const auto& display = display_manager()->GetDisplayAt(0);
TriggerIndicator(display, EdgeType::kTop);
CheckIndicatorShown(2, display);
}
TEST_F(DisplayAlignmentControllerTest, TriggerSecondaryDisplay) {
UpdateDisplay("1920x1080,1366x768,800x600");
const auto& display = display_manager()->GetDisplayAt(1);
TriggerIndicator(display, EdgeType::kTop);
CheckIndicatorShown(4, display);
}
TEST_F(DisplayAlignmentControllerTest, TriggerTwoDisplayOnSameEdge) {
int64_t primary_id = display::Screen::Get()->GetPrimaryDisplay().id();
display::DisplayIdList list =
display::test::CreateDisplayIdListN(primary_id, 3);
display::DisplayLayoutBuilder builder(primary_id);
builder.AddDisplayPlacement(list[1], primary_id,
display::DisplayPlacement::TOP, -110);
builder.AddDisplayPlacement(list[2], primary_id,
display::DisplayPlacement::TOP, 490);
display_manager()->layout_store()->RegisterLayoutForDisplayIdList(
list, builder.Build());
UpdateDisplay("1200x500,600x500,600x500");
const auto& display = display_manager()->GetDisplayAt(1);
TriggerIndicator(display, EdgeType::kTop);
CheckIndicatorShown(4, display);
}
TEST_F(DisplayAlignmentControllerTest, DontTriggerIndicator) {
UpdateDisplay("1920x1080,1366x768");
const auto& display = display_manager()->GetDisplayAt(0);
aura::Window* root_window =
Shell::Get()->window_tree_host_manager()->GetRootWindowForDisplayId(
display.id());
ui::test::EventGenerator generator(root_window);
generator.MoveMouseToInHost(gfx::Point(0, 0));
generator.MoveMouseToInHost(gfx::Point(20, 50));
EXPECT_TRUE(NoIndicatorsExist());
mock_timer_ptr_->Fire();
EXPECT_TRUE(NoIndicatorsExist());
}
TEST_F(DisplayAlignmentControllerTest, DontTriggerIndicatorDifferentDisplays) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
const auto& secondary_display = display_manager()->GetDisplayAt(1);
WindowTreeHostManager* window_tree_host_manager =
Shell::Get()->window_tree_host_manager();
aura::Window* primary_root =
window_tree_host_manager->GetRootWindowForDisplayId(primary_display.id());
aura::Window* secondary_root =
window_tree_host_manager->GetRootWindowForDisplayId(
secondary_display.id());
ui::test::EventGenerator primary_generator(primary_root);
ui::test::EventGenerator secondary_generator(secondary_root);
primary_generator.MoveMouseToInHost(gfx::Point(0, 0));
primary_generator.MoveMouseToInHost(gfx::Point(20, 20));
secondary_generator.MoveMouseToInHost(gfx::Point(1365, 0));
EXPECT_TRUE(NoIndicatorsExist());
}
TEST_F(DisplayAlignmentControllerTest, RemoveDisplay) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
TriggerIndicator(primary_display, EdgeType::kLeft);
CheckIndicatorShown(2, primary_display);
UpdateDisplay("1920x1080");
EXPECT_TRUE(NoIndicatorsExist());
TriggerIndicator(primary_display, EdgeType::kTop);
EXPECT_TRUE(NoIndicatorsExist());
}
TEST_F(DisplayAlignmentControllerTest, LockScreen) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
TriggerIndicator(primary_display, EdgeType::kBottom);
CheckIndicatorShown(2, primary_display);
LockScreen();
EXPECT_TRUE(NoIndicatorsExist());
TriggerIndicator(primary_display, EdgeType::kTop);
EXPECT_TRUE(NoIndicatorsExist());
UnlockScreen();
TriggerIndicator(primary_display, EdgeType::kTop);
CheckIndicatorShown(2, primary_display);
}
TEST_F(DisplayAlignmentControllerTest, ChangeResolution) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
TriggerIndicator(primary_display, EdgeType::kTop);
CheckIndicatorShown(2, primary_display);
UpdateDisplay("2560x1440,1366x768");
EXPECT_TRUE(NoIndicatorsExist());
TriggerIndicator(primary_display, EdgeType::kRight);
CheckIndicatorShown(2, primary_display);
}
TEST_F(DisplayAlignmentControllerTest, AllowOffByOnes) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
WindowTreeHostManager* window_tree_host_manager =
Shell::Get()->window_tree_host_manager();
aura::Window* primary_root =
window_tree_host_manager->GetRootWindowForDisplayId(primary_display.id());
ui::test::EventGenerator primary_generator(primary_root);
primary_generator.MoveMouseToInHost(gfx::Point(1, 1));
primary_generator.MoveMouseToInHost(gfx::Point(20, 20));
primary_generator.MoveMouseToInHost(gfx::Point(1, 1));
CheckIndicatorShown(2, primary_display);
}
TEST_F(DisplayAlignmentControllerTest, DragDisplayBasic) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
const auto& secondary_display = display_manager()->GetDisplayAt(1);
DragDisplay(primary_display.id(), 0, 0);
CheckPreviewIndicatorShown(primary_display.id(), secondary_display.id(),
true);
UpdateDisplay("1920x1080,1366x768");
EXPECT_TRUE(NoIndicatorsExist());
}
TEST_F(DisplayAlignmentControllerTest, DragDisplayReplaceExistingIndicators) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
const auto& secondary_display = display_manager()->GetDisplayAt(1);
TriggerIndicator(primary_display, EdgeType::kLeft);
CheckIndicatorShown(2, primary_display);
DragDisplay(primary_display.id(), 0, 10);
CheckPreviewIndicatorShown(primary_display.id(), secondary_display.id(),
true);
}
TEST_F(DisplayAlignmentControllerTest, DragDisplayHideOldNeighbors) {
UpdateDisplay("1920x1080,1366x768");
const auto& primary_display = display_manager()->GetDisplayAt(0);
const auto& secondary_display = display_manager()->GetDisplayAt(1);
DragDisplay(primary_display.id(), 0, 0);
DragDisplay(primary_display.id(), -2000, -2000);
CheckPreviewIndicatorShown(primary_display.id(), secondary_display.id(),
false);
}
TEST_F(DisplayAlignmentControllerTest, DragDisplayNewNeighbor) {
UpdateDisplay("1000x900,1000x900,1000x100");
const auto& display_1 = display_manager()->GetDisplayAt(0);
const auto& display_2 = display_manager()->GetDisplayAt(1);
const auto& display_3 = display_manager()->GetDisplayAt(2);
DragDisplay(display_1.id(), 0, 0);
DragDisplay(display_1.id(), 3000, 0);
CheckPreviewIndicatorShown(display_1.id(), display_2.id(),
false);
CheckPreviewIndicatorShown(display_1.id(), display_3.id(),
true);
}
}