#include "ash/wm/overview/overview_window_drag_controller.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desk_mini_view.h"
#include "ash/wm/desks/desks_constants.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_histogram_enums.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/overview_desk_bar_view.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_drop_target.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_grid_test_api.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_item_base.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_drag_indicators.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_util.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/window_tree_host.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
void StartDraggingItemBy(OverviewItemBase* item,
int x,
int y,
bool by_touch_gestures,
ui::test::EventGenerator* event_generator) {
const gfx::Point item_center =
gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
event_generator->set_current_screen_location(item_center);
if (by_touch_gestures) {
event_generator->PressTouch();
event_generator->MoveTouchBy(x, y);
} else {
event_generator->PressLeftButton();
event_generator->MoveMouseBy(x, y);
}
}
gfx::Point GetScreenInPixelsPoint(int x, int y) {
gfx::Point point{x, y};
Shell::GetPrimaryRootWindow()->GetHost()->ConvertDIPToScreenInPixels(&point);
return point;
}
}
class OverviewWindowDragControllerTest : public AshTestBase {
public:
OverviewWindowDragControllerTest() = default;
OverviewWindowDragControllerTest(const OverviewWindowDragControllerTest&) =
delete;
OverviewWindowDragControllerTest& operator=(
const OverviewWindowDragControllerTest&) = delete;
~OverviewWindowDragControllerTest() override = default;
SplitViewController* split_view_controller() {
return SplitViewController::Get(Shell::GetPrimaryRootWindow());
}
OverviewSession* overview_session() {
return OverviewController::Get()->overview_session();
}
OverviewWindowDragController* drag_controller() {
return overview_session()->window_drag_controller();
}
const SplitViewDragIndicators* drag_indicators() const {
return OverviewController::Get()
->overview_session()
->grid_list()[0]
->split_view_drag_indicators();
}
OverviewGrid* overview_grid() {
return overview_session()->GetGridWithRootWindow(
Shell::GetPrimaryRootWindow());
}
const views::Widget* desks_bar_widget() {
DCHECK(overview_grid()->desks_bar_view());
return overview_grid()->desks_bar_view()->GetWidget();
}
OverviewItemBase* GetOverviewItemForWindow(aura::Window* window) {
return overview_session()->GetOverviewItemForWindow(window);
}
int GetExpectedDesksBarShiftAmount() const {
return drag_indicators()->GetLeftHighlightViewBounds().bottom() +
kHighlightScreenEdgePaddingDp;
}
void StartDraggingAndValidateDesksBarShifted(aura::Window* window) {
EnterOverview();
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
auto* overview_item = GetOverviewItemForWindow(window);
ASSERT_TRUE(overview_item);
StartDraggingItemBy(overview_item, 100, 200, false,
GetEventGenerator());
ASSERT_TRUE(drag_controller());
EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
drag_controller()->current_drag_behavior_for_testing());
ASSERT_TRUE(drag_indicators());
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
drag_indicators()->current_window_dragging_state());
EXPECT_EQ(GetExpectedDesksBarShiftAmount(),
desks_bar_widget()->GetWindowBoundsInScreen().y());
}
int GetDesksBarViewExpandedStateHeight(
const OverviewDeskBarView* desks_bar_view) {
return DeskBarViewBase::GetPreferredBarHeight(
desks_bar_view->GetWidget()->GetNativeWindow()->GetRootWindow(),
DeskBarViewBase::Type::kOverview, DeskBarViewBase::State::kExpanded);
}
};
TEST_F(OverviewWindowDragControllerTest, NoDragToCloseUsingMouse) {
auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
base::RunLoop().RunUntilIdle();
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
auto* overview_controller = OverviewController::Get();
EnterOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
auto* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window.get());
ASSERT_TRUE(overview_item);
const gfx::RectF target_bounds_before_drag = overview_item->target_bounds();
auto* event_generator = GetEventGenerator();
StartDraggingItemBy(overview_item, 30, 200, false,
event_generator);
OverviewWindowDragController* drag_controller =
overview_session->window_drag_controller();
EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
drag_controller->current_drag_behavior_for_testing());
event_generator->ReleaseLeftButton();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
}
TEST_F(OverviewWindowDragControllerTest, DropTargetBoundsTest) {
auto* desk_controller = DesksController::Get();
desk_controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, desk_controller->desks().size());
std::unique_ptr<aura::Window> window = CreateAppWindow();
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->item_list();
ASSERT_EQ(window_list.size(), 1u);
OverviewSession* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window.get());
auto* event_generator = GetEventGenerator();
const gfx::RectF target_bounds_before_dragging =
overview_item->target_bounds();
for (const bool by_touch : {false, true}) {
DragItemToPoint(
overview_item,
Shell::GetPrimaryRootWindow()->GetBoundsInScreen().CenterPoint(),
event_generator, by_touch, false);
EXPECT_TRUE(overview_controller->InOverviewSession());
const OverviewItemBase* drop_target = overview_grid->drop_target();
EXPECT_TRUE(drop_target);
EXPECT_EQ(gfx::RectF(drop_target->item_widget()->GetWindowBoundsInScreen()),
target_bounds_before_dragging);
if (by_touch) {
event_generator->ReleaseTouch();
} else {
event_generator->ReleaseLeftButton();
}
}
}
TEST_F(OverviewWindowDragControllerTest,
SwitchDragToCloseToNormalDragWhenDraggedToDesk) {
UpdateDisplay("600x800");
auto* controller = DesksController::Get();
controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, controller->desks().size());
auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
auto* overview_controller = Shell::Get()->overview_controller();
EnterOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
auto* overview_session = overview_controller->overview_session();
const auto* overview_grid =
overview_session->GetGridWithRootWindow(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto* desks_bar_view = overview_grid->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
auto* overview_item =
overview_session->GetOverviewItemForWindow(window.get());
ASSERT_TRUE(overview_item);
const gfx::RectF target_bounds_before_drag = overview_item->target_bounds();
const int item_center_to_desks_bar_bottom =
gfx::ToRoundedPoint(target_bounds_before_drag.CenterPoint()).y() -
desks_bar_view->GetBoundsInScreen().bottom();
EXPECT_GT(item_center_to_desks_bar_bottom, 0);
const int space_to_leave = 20;
auto* event_generator = GetEventGenerator();
StartDraggingItemBy(overview_item, 0,
-(item_center_to_desks_bar_bottom - space_to_leave),
true, event_generator);
OverviewWindowDragController* drag_controller =
overview_session->window_drag_controller();
EXPECT_EQ(OverviewWindowDragController::DragBehavior::kDragToClose,
drag_controller->current_drag_behavior_for_testing());
event_generator->MoveTouchBy(0, -(space_to_leave + 10));
EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
drag_controller->current_drag_behavior_for_testing());
auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
ASSERT_TRUE(desk_2_mini_view);
event_generator->MoveTouch(
desk_2_mini_view->GetBoundsInScreen().CenterPoint());
event_generator->ReleaseTouch();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_grid->empty());
const Desk* desk_2 = controller->GetDeskAtIndex(1);
EXPECT_TRUE(base::Contains(desk_2->windows(), window.get()));
EXPECT_TRUE(const_cast<OverviewGrid*>(overview_grid)->no_windows_widget());
}
TEST_F(OverviewWindowDragControllerTest, WindowDestroyedDuringDragging) {
std::unique_ptr<aura::Window> window =
CreateAppWindow(gfx::Rect(0, 0, 250, 100));
auto* overview_controller = Shell::Get()->overview_controller();
EnterOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
auto* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window.get());
ASSERT_TRUE(overview_item);
auto* event_generator = GetEventGenerator();
StartDraggingItemBy(overview_item, 30, 200, false,
event_generator);
OverviewWindowDragController* drag_controller =
overview_session->window_drag_controller();
EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
drag_controller->current_drag_behavior_for_testing());
window.reset();
EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNoDrag,
drag_controller->current_drag_behavior_for_testing());
}
TEST_F(OverviewWindowDragControllerTest,
DragAndDropWindowInPortraitModeWithOneDesk) {
UpdateDisplay("768x1000");
auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
StartDraggingAndValidateDesksBarShifted(window.get());
const auto* desks_bar_view = overview_grid()->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
desks_bar_view->bounds().height());
auto* event_generator = GetEventGenerator();
event_generator->ReleaseLeftButton();
EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
desks_bar_view->bounds().height());
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
LeftClickOn(desks_bar_view->new_desk_button());
EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
desks_bar_view->bounds().height());
auto* controller = Shell::Get()->desks_controller();
controller->RemoveDesk(controller->desks().back().get(),
DesksCreationRemovalSource::kButton,
DeskCloseType::kCombineDesks);
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
EXPECT_EQ(DeskBarViewBase::State::kExpanded, desks_bar_view->state());
EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
desks_bar_view->bounds().height());
}
TEST_F(OverviewWindowDragControllerTest, DragWindowInPortraitMode) {
UpdateDisplay("768x1000");
std::vector<std::unique_ptr<aura::Window>> windows;
for (int i = 0; i < 10; ++i)
windows.push_back(CreateAppWindow(gfx::Rect(0, 0, 768, 1269)));
StartDraggingAndValidateDesksBarShifted(windows.back().get());
const auto* desks_bar_view = overview_grid()->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
EXPECT_FALSE(
desks_bar_view->GetBoundsInScreen().Intersects(gfx::ToEnclosedRect(
overview_grid()->item_list()[1].get()->target_bounds())));
}
TEST_F(OverviewWindowDragControllerTest, DesksBarState) {
UpdateDisplay("800x600, 800x600");
std::unique_ptr<aura::Window> window = CreateAppWindow();
EnterOverview();
ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
aura::Window::Windows roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
for (aura::Window* root : roots) {
const auto* desks_bar_view = GetOverviewGridForRoot(root)->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
ASSERT_EQ(DeskBarViewBase::State::kZero, desks_bar_view->state());
}
auto* overview_item = GetOverviewItemForWindow(window.get());
ASSERT_TRUE(overview_item);
StartDraggingItemBy(overview_item, 5, 5,
false, GetEventGenerator());
for (aura::Window* root : roots) {
const auto* desks_bar_view = GetOverviewGridForRoot(root)->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
EXPECT_EQ(DeskBarViewBase::State::kExpanded, desks_bar_view->state());
}
}
class OverviewWindowDragControllerDesksPortraitTabletTest
: public OverviewWindowDragControllerTest {
public:
OverviewWindowDragControllerDesksPortraitTabletTest() = default;
OverviewWindowDragControllerDesksPortraitTabletTest(
const OverviewWindowDragControllerDesksPortraitTabletTest&) = delete;
OverviewWindowDragControllerDesksPortraitTabletTest& operator=(
const OverviewWindowDragControllerDesksPortraitTabletTest&) = delete;
~OverviewWindowDragControllerDesksPortraitTabletTest() override = default;
void SetUp() override {
OverviewWindowDragControllerTest::SetUp();
UpdateDisplay("800x700");
const int64_t display_id = display::Screen::Get()->GetPrimaryDisplay().id();
set_internal_ = std::make_unique<display::test::ScopedSetInternalDisplayId>(
display_manager(), display_id);
ScreenOrientationControllerTestApi test_api(
Shell::Get()->screen_orientation_controller());
test_api.SetDisplayRotation(display::Display::ROTATE_270,
display::Display::RotationSource::ACTIVE);
EXPECT_EQ(test_api.GetCurrentOrientation(),
chromeos::OrientationType::kPortraitPrimary);
base::RunLoop().RunUntilIdle();
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
auto* desks_controller = DesksController::Get();
desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, desks_controller->desks().size());
desks_controller->GetDeskAtIndex(1)->SetName(u"Desk 2", false);
}
private:
std::unique_ptr<display::test::ScopedSetInternalDisplayId> set_internal_;
};
TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest,
DragAndDropInEmptyArea) {
auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
StartDraggingAndValidateDesksBarShifted(window.get());
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 400));
event_generator->ReleaseLeftButton();
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
EXPECT_EQ(0, desks_bar_widget()->GetWindowBoundsInScreen().y());
}
TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest,
DragAndDropInSnapAreas) {
auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
StartDraggingAndValidateDesksBarShifted(window.get());
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 800));
ASSERT_TRUE(drag_indicators());
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapSecondary,
drag_indicators()->current_window_dragging_state());
EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
desks_bar_widget()->GetWindowBoundsInScreen().y());
event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 400));
ASSERT_TRUE(drag_indicators());
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
drag_indicators()->current_window_dragging_state());
EXPECT_EQ(GetExpectedDesksBarShiftAmount(),
desks_bar_widget()->GetWindowBoundsInScreen().y());
event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 0));
ASSERT_TRUE(drag_indicators());
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
drag_indicators()->current_window_dragging_state());
EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
desks_bar_widget()->GetWindowBoundsInScreen().y());
event_generator->ReleaseLeftButton();
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
split_view_controller()->state());
EXPECT_EQ(window.get(), split_view_controller()->primary_window());
EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
desks_bar_widget()->GetWindowBoundsInScreen().y());
}
TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest, DragAndDropInDesk) {
auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
StartDraggingAndValidateDesksBarShifted(window.get());
const auto* desks_bar_view = overview_grid()->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
ASSERT_TRUE(desk_2_mini_view);
const auto mini_view_location =
desk_2_mini_view->GetBoundsInScreen().CenterPoint();
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
GetScreenInPixelsPoint(mini_view_location.x(), mini_view_location.y()));
ASSERT_TRUE(drag_indicators());
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
drag_indicators()->current_window_dragging_state());
EXPECT_EQ(GetExpectedDesksBarShiftAmount(),
desks_bar_widget()->GetWindowBoundsInScreen().y());
EXPECT_TRUE(desks_util::BelongsToActiveDesk(window.get()));
event_generator->ReleaseLeftButton();
EXPECT_FALSE(desks_util::BelongsToActiveDesk(window.get()));
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
desks_bar_widget()->GetWindowBoundsInScreen().y());
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag,
drag_indicators()->current_window_dragging_state());
}
TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest,
DragWindowInPortraitMode) {
UpdateDisplay("700x1000");
std::vector<std::unique_ptr<aura::Window>> windows;
for (int i = 0; i < 9; ++i) {
windows.push_back(CreateAppWindow());
}
StartDraggingAndValidateDesksBarShifted(windows[4].get());
auto* desks_controller = DesksController::Get();
DesksController::Get()->RemoveDesk(desks_controller->GetDeskAtIndex(1),
DesksCreationRemovalSource::kButton,
DeskCloseType::kCombineDesks);
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
const auto* desks_bar_view = overview_grid()->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
const gfx::Rect desk_bar_bounds = desks_bar_view->GetBoundsInScreen();
const gfx::Rect first_item_bounds =
gfx::ToEnclosedRect(overview_grid()->item_list()[0]->target_bounds());
EXPECT_NEAR(desk_bar_bounds.bottom(), first_item_bounds.y(), 20);
}
}