chromium/ash/wm/overview/overview_session_unittest.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/wm/overview/overview_session.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accelerators/exit_warning_handler.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/magnifier/docked_magnifier_controller.h"
#include "ash/accessibility/test_accessibility_controller_client.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/frame_throttler/mock_frame_throttling_observer.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shelf_prefs.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/close_button.h"
#include "ash/style/rounded_label_widget.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/raster_scale_change_tracker.h"
#include "ash/test/test_window_builder.h"
#include "ash/wallpaper/views/wallpaper_view.h"
#include "ash/wallpaper/views/wallpaper_widget_controller.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desk_action_button.h"
#include "ash/wm/desks/desk_action_context_menu.h"
#include "ash/wm/desks/desk_action_view.h"
#include "ash/wm/desks/desk_bar_view_base.h"
#include "ash/wm/desks/desk_icon_button.h"
#include "ash/wm/desks/desks_constants.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_test_api.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/overview_desk_bar_view.h"
#include "ash/wm/desks/templates/saved_desk_save_desk_button.h"
#include "ash/wm/desks/templates/saved_desk_util.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/gestures/back_gesture/back_gesture_event_handler.h"
#include "ash/wm/gestures/wm_gesture_handler.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_constants.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_event_handler.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_item_view.h"
#include "ash/wm/overview/overview_test_base.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/overview_window_drag_controller.h"
#include "ash/wm/overview/scoped_overview_transform_window.h"
#include "ash/wm/resize_shadow.h"
#include "ash/wm/resize_shadow_controller.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_divider.h"
#include "ash/wm/splitview/split_view_drag_indicators.h"
#include "ash/wm/splitview/split_view_test_util.h"
#include "ash/wm/splitview/split_view_types.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/test/fake_window_state.h"
#include "ash/wm/window_mini_view_header_view.h"
#include "ash/wm/window_preview_view.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_state_delegate.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_constants.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/wm_metrics.h"
#include "ash/wm/workspace/workspace_window_resizer.h"
#include "base/containers/contains.h"
#include "base/containers/to_vector.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/draw_waiter_for_test.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/compositor/test/layer_animator_test_controller.h"
#include "ui/compositor/test/test_utils.h"
#include "ui/display/display_layout.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/cursor_manager.h"
#include "ui/wm/core/shadow_controller.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

using ::chromeos::WindowStateType;

constexpr const char kActiveWindowChangedFromOverview[] =
    "WindowSelector_ActiveWindowChanged";

constexpr gfx::Rect kInitWindowBoundsToGrow(80, 80);
constexpr gfx::Rect kInitWindowBoundsToShrink(600, 600);

class TweenTester : public ui::LayerAnimationObserver {
 public:
  explicit TweenTester(aura::Window* window) : window_(window) {
    window->layer()->GetAnimator()->AddObserver(this);
  }

  TweenTester(const TweenTester&) = delete;
  TweenTester& operator=(const TweenTester&) = delete;

  ~TweenTester() override {
    window_->layer()->GetAnimator()->RemoveObserver(this);
    EXPECT_TRUE(will_animate_);
  }

  // ui::LayerAnimationObserver:
  void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {}
  void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {}
  void OnLayerAnimationScheduled(
      ui::LayerAnimationSequence* sequence) override {}
  void OnAttachedToSequence(ui::LayerAnimationSequence* sequence) override {
    ui::LayerAnimationObserver::OnAttachedToSequence(sequence);
    if (!will_animate_) {
      tween_type_ = sequence->FirstElement()->tween_type();
      will_animate_ = true;
    }
  }

  gfx::Tween::Type tween_type() const { return tween_type_; }

 private:
  gfx::Tween::Type tween_type_ = gfx::Tween::LINEAR;
  raw_ptr<aura::Window> window_;
  bool will_animate_ = false;
};

// Class which tracks if a given widget has been destroyed.
class TestDestroyedWidgetObserver : public views::WidgetObserver {
 public:
  explicit TestDestroyedWidgetObserver(views::Widget* widget) {
    DCHECK(widget);
    observation_.Observe(widget);
  }
  TestDestroyedWidgetObserver(const TestDestroyedWidgetObserver&) = delete;
  TestDestroyedWidgetObserver& operator=(const TestDestroyedWidgetObserver&) =
      delete;
  ~TestDestroyedWidgetObserver() override = default;

  // views::WidgetObserver:
  void OnWidgetDestroyed(views::Widget* widget) override {
    DCHECK(!widget_destroyed_);
    widget_destroyed_ = true;
    observation_.Reset();
  }

  bool widget_destroyed() const { return widget_destroyed_; }

 private:
  bool widget_destroyed_ = false;

  base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
      this};
};

void CombineDesksViaMiniView(const DeskMiniView* desk_mini_view,
                             ui::test::EventGenerator* event_generator) {
  CHECK(desk_mini_view);

  // Move to the center of the mini view so that the combine button shows up.
  const gfx::Point mini_view_center =
      desk_mini_view->GetBoundsInScreen().CenterPoint();
  event_generator->MoveMouseTo(mini_view_center);
  const DeskActionView* desk_action_view = desk_mini_view->desk_action_view();
  auto* combine_button = desk_action_view->combine_desks_button();
  EXPECT_TRUE(combine_button->GetVisible());

  // Move to the center of the combine button and click.
  event_generator->MoveMouseTo(
      combine_button->GetBoundsInScreen().CenterPoint());
  event_generator->ClickLeftButton();
}

std::string OverviewSessionTestParamsToString(
    const testing::TestParamInfo<std::tuple<bool, bool>>& info) {
  const auto& [desk_templates, has_snapshot] = info.param;
  std::string name = desk_templates ? "DesksTemplatesOn" : "DesksTemplatesOff";
  name += has_snapshot ? "_SnapshotOn" : "_SnapshotOff";
  return name;
}

}  // namespace

class OverviewSessionTest
    : public OverviewTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  OverviewSessionTest() = default;
  OverviewSessionTest(const OverviewSessionTest&) = delete;
  OverviewSessionTest& operator=(const OverviewSessionTest&) = delete;
  ~OverviewSessionTest() override = default;

  // Used for tests regarding the exit warning popup.
  void StubForTest(ExitWarningHandler* ewh) {
    ewh->stub_timer_for_test_ = true;
  }
  bool IsUIShown(ExitWarningHandler* ewh) { return !!ewh->widget_; }

  bool DeskTemplatesOn() const { return std::get<0>(GetParam()); }

  bool SnapshotOn() const { return std::get<1>(GetParam()); }

  // OverviewTestBase:
  void SetUp() override {
    scoped_feature_list_.InitWithFeatureStates(
        {{features::kDesksTemplates, DeskTemplatesOn()},
         {features::kSnapGroup, true},
         {features::kDeskBarWindowOcclusionOptimization, true}});

    OverviewTestBase::SetUp();
    Shell::Get()->overview_controller()->set_windows_have_snapshot_for_test(
        SnapshotOn());
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests that close buttons on windows in overview do not work
// when one window is being dragged.
TEST_P(OverviewSessionTest, CloseButtonDisabledOnDrag) {
  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));

  aura::Window* window1 = widget1->GetNativeWindow();
  aura::Window* window2 = widget2->GetNativeWindow();

  ToggleOverview();

  ASSERT_FALSE(widget1->IsClosed());
  ASSERT_FALSE(widget2->IsClosed());

  auto* item1 = GetOverviewItemForWindow(window1);
  auto* item2 = GetOverviewItemForWindow(window2);

  // Get location of close button on `window1` before drag.
  const gfx::Point item1_close_button_position =
      GetCloseButton(item1)->GetBoundsInScreen().CenterPoint();

  // Drag `window1` in overview to trigger drag animations.
  GetEventGenerator()->PressTouchId(
      /*touch_id=*/0,
      gfx::ToRoundedPoint(item1->GetTransformedBounds().CenterPoint()));
  GetEventGenerator()->MoveTouchIdBy(/*touch_id=*/0, -100, 0);

  // Close button for both items has 0 opacity.
  EXPECT_EQ(1.f, GetTitlebarOpacity(item1));
  EXPECT_EQ(0.f, GetCloseButtonOpacity(item1));
  EXPECT_EQ(1.f, GetTitlebarOpacity(item2));
  EXPECT_EQ(0.f, GetCloseButtonOpacity(item2));

  // Both close buttons should be disabled at this point.
  EXPECT_FALSE(GetCloseButton(item1)->GetEnabled());
  EXPECT_FALSE(GetCloseButton(item2)->GetEnabled());

  // Try to close `window2` and `window1`.
  GetEventGenerator()->GestureTapAt(
      GetCloseButton(item2)->GetBoundsInScreen().CenterPoint());
  GetEventGenerator()->GestureTapAt(item1_close_button_position);
  GetEventGenerator()->GestureTapAt(
      GetCloseButton(item1)->GetBoundsInScreen().CenterPoint());

  // Check that both windows are still open.
  ASSERT_FALSE(widget1->IsClosed());
  ASSERT_FALSE(widget2->IsClosed());

  // Release touch 0 to exit drag.
  GetEventGenerator()->ReleaseTouchId(0);

  // We should still be in an overview session.
  ASSERT_TRUE(InOverviewSession());

  // The windows should now be closable.
  GetEventGenerator()->GestureTapAt(
      GetCloseButton(item2)->GetBoundsInScreen().CenterPoint());
  GetEventGenerator()->GestureTapAt(
      GetCloseButton(item1)->GetBoundsInScreen().CenterPoint());

  EXPECT_TRUE(widget1->IsClosed());
  EXPECT_TRUE(widget2->IsClosed());
}

// Tests that close buttons on windows in overview are re-enabled
// when one window is snapped to a side of the screen.
TEST_P(OverviewSessionTest, CloseButtonEnabledOnSnap) {
  std::unique_ptr<views::Widget> widget2 =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);

  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  aura::Window* window2 = widget2->GetNativeWindow();

  ToggleOverview();

  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2);

  ASSERT_FALSE(widget2->IsClosed());

  ASSERT_TRUE(GetSplitViewController()->CanSnapWindow(
      window1.get(), chromeos::kDefaultSnapRatio));

  // Snap `window1` to the left side of the screen while in
  // overview.
  GetEventGenerator()->PressTouchId(
      /*touch_id=*/0,
      gfx::ToRoundedPoint(item1->GetTransformedBounds().CenterPoint()));

  GetEventGenerator()->MoveTouchId(gfx::Point(0, 0), /*touch_id=*/0);

  EXPECT_FALSE(GetCloseButton(item1)->GetEnabled());
  EXPECT_FALSE(GetCloseButton(item2)->GetEnabled());

  GetEventGenerator()->ReleaseTouchId(0);

  ASSERT_TRUE(InOverviewSession());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            GetSplitViewController()->state());

  // The close button for `window2` should be enabled.
  EXPECT_TRUE(GetCloseButton(item2)->GetEnabled());

  // Try to close `window2`.
  GetEventGenerator()->GestureTapAt(
      GetCloseButton(item2)->GetBoundsInScreen().CenterPoint());

  // Check that `window2` is closed.
  EXPECT_TRUE(widget2->IsClosed());
}

// Tests that an a11y alert is sent on entering overview mode.
TEST_P(OverviewSessionTest, A11yAlertOnOverviewMode) {
  TestAccessibilityControllerClient client;
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  EXPECT_NE(AccessibilityAlert::WINDOW_OVERVIEW_MODE_ENTERED,
            client.last_a11y_alert());
  ToggleOverview();
  EXPECT_EQ(AccessibilityAlert::WINDOW_OVERVIEW_MODE_ENTERED,
            client.last_a11y_alert());
}

// Tests that there are no crashes when there is not enough screen space
// available to show all of the windows.
TEST_P(OverviewSessionTest, SmallDisplay) {
  UpdateDisplay("3x1");
  gfx::Rect bounds(0, 0, 1, 1);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds));
  window1->SetProperty(aura::client::kTopViewInset, 0);
  window2->SetProperty(aura::client::kTopViewInset, 0);
  window3->SetProperty(aura::client::kTopViewInset, 0);
  window4->SetProperty(aura::client::kTopViewInset, 0);
  ToggleOverview();
}

// Tests entering overview mode with two windows and selecting one by clicking.
TEST_P(OverviewSessionTest, Basic) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);

  // Overview disabled by default.
  EXPECT_FALSE(InOverviewSession());

  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get()));
  wm::ActivateWindow(window2.get());
  EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
  EXPECT_EQ(window2.get(), window_util::GetFocusedWindow());

  // Hide the cursor before entering overview to test that it will be shown.
  aura::client::GetCursorClient(root_window)->HideCursor();

  CheckOverviewEnterExitHistogram("Init", {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0});
  // In overview mode the windows should no longer overlap and the overview
  // focus window should be focused.
  ToggleOverview();

  // Warms up the compositor so that UI changes are picked up in time before
  // throughput tracker is stopped.
  ui::Compositor* const compositor = window1->GetHost()->compositor();
  compositor->ScheduleFullRedraw();
  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));

  WaitForOverviewEnterAnimation();

  EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(),
            window_util::GetFocusedWindow());
  EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get()));
  CheckOverviewEnterExitHistogram("Enter", {1, 0, 0, 0, 0}, {0, 0, 0, 0, 0});

  // Clicking window 1 should activate it.
  ClickWindow(window1.get());

  // Warms up the compositor so that UI changes are picked up in time before
  // throughput tracker is stopped.
  compositor->ScheduleFullRedraw();
  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));

  WaitForOverviewExitAnimation();

  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
  EXPECT_EQ(window1.get(), window_util::GetFocusedWindow());

  // Cursor should have been unlocked.
  EXPECT_FALSE(aura::client::GetCursorClient(root_window)->IsCursorLocked());

  CheckOverviewEnterExitHistogram("Exit", {1, 0, 0, 0, 0}, {1, 0, 0, 0, 0});
}

// Tests activating minimized window.
TEST_P(OverviewSessionTest, ActivateMinimized) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  WindowState* window_state = WindowState::Get(window.get());
  WMEvent minimize_event(WM_EVENT_MINIMIZE);
  window_state->OnWMEvent(&minimize_event);
  EXPECT_FALSE(window->IsVisible());
  EXPECT_EQ(0.f, window->layer()->GetTargetOpacity());
  EXPECT_EQ(WindowStateType::kMinimized,
            WindowState::Get(window.get())->GetStateType());

  ToggleOverview();

  EXPECT_FALSE(window->IsVisible());
  EXPECT_EQ(0.f, window->layer()->GetTargetOpacity());
  EXPECT_EQ(WindowStateType::kMinimized, window_state->GetStateType());
  WindowPreviewView* preview_view =
      GetPreviewView(GetOverviewItemForWindow(window.get()));
  EXPECT_TRUE(preview_view);

  const gfx::Point point = preview_view->GetBoundsInScreen().CenterPoint();
  GetEventGenerator()->set_current_screen_location(point);
  GetEventGenerator()->ClickLeftButton();

  EXPECT_FALSE(InOverviewSession());

  EXPECT_TRUE(window->IsVisible());
  EXPECT_EQ(1.f, window->layer()->GetTargetOpacity());
  EXPECT_EQ(WindowStateType::kNormal, window_state->GetStateType());
}

// A window can be minimized when losing a focus upon entering overview.
// If such window was active, it will be unminimized when exiting overview.
// b/163551595.
TEST_P(OverviewSessionTest, MinimizeDuringOverview) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  ToggleOverview();
  WindowState* window_state = WindowState::Get(window.get());
  WMEvent minimize_event(WM_EVENT_MINIMIZE);
  window_state->OnWMEvent(&minimize_event);
  EXPECT_FALSE(window->IsVisible());
  EXPECT_EQ(WindowStateType::kMinimized,
            WindowState::Get(window.get())->GetStateType());
  ToggleOverview();
}

// Tests that the ordering of windows is stable across different overview
// sessions even when the windows have the same bounds.
TEST_P(OverviewSessionTest, WindowsOrder) {
  std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
  std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2));
  std::unique_ptr<aura::Window> window3(CreateTestWindowInShellWithId(3));

  // The order of windows in overview mode is MRU.
  WindowState::Get(window1.get())->Activate();
  ToggleOverview();
  const std::vector<std::unique_ptr<OverviewItemBase>>& overview1 =
      GetOverviewItemsForRoot(0);
  EXPECT_EQ(1, overview1[0]->GetWindow()->GetId());
  EXPECT_EQ(3, overview1[1]->GetWindow()->GetId());
  EXPECT_EQ(2, overview1[2]->GetWindow()->GetId());
  ToggleOverview();

  // Activate the second window.
  WindowState::Get(window2.get())->Activate();
  ToggleOverview();
  const std::vector<std::unique_ptr<OverviewItemBase>>& overview2 =
      GetOverviewItemsForRoot(0);

  // The order should be MRU.
  EXPECT_EQ(2, overview2[0]->GetWindow()->GetId());
  EXPECT_EQ(1, overview2[1]->GetWindow()->GetId());
  EXPECT_EQ(3, overview2[2]->GetWindow()->GetId());
  ToggleOverview();
}

// Tests selecting a window by tapping on it.
TEST_P(OverviewSessionTest, BasicGesture) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  wm::ActivateWindow(window1.get());
  EXPECT_EQ(window1.get(), window_util::GetFocusedWindow());
  ToggleOverview();
  EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(),
            window_util::GetFocusedWindow());
  GetEventGenerator()->GestureTapAt(
      GetTransformedTargetBounds(window2.get()).CenterPoint());
  EXPECT_EQ(window2.get(), window_util::GetFocusedWindow());
}

// Tests that calling `views::Widget::CloseNow` on a minimized window that is
// currently being dragged does not cause a crash. Regression test for
// b/268413746.
TEST_P(OverviewSessionTest, CloseNowDraggedMinimizedWindow) {
  std::unique_ptr<aura::Window> window = CreateAppWindow();
  WindowState::Get(window.get())->Minimize();

  // Start dragging the window.
  ToggleOverview();
  GetEventGenerator()->set_current_screen_location(
      GetTransformedTargetBounds(window.get()).CenterPoint());
  GetEventGenerator()->PressLeftButton();

  // Call `views::Widget::CloseNow` on the window mid drag and verify no crash.
  // This could happen in production when an exo window shuts down.
  views::Widget::GetWidgetForNativeView(window.release())->CloseNow();
}

// Tests that the user action WindowSelector_ActiveWindowChanged is
// recorded when the mouse/touchscreen/keyboard are used to select a window
// in overview mode which is different from the previously-active window.
TEST_P(OverviewSessionTest, ActiveWindowChangedUserActionRecorded) {
  base::UserActionTester user_action_tester;
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  wm::ActivateWindow(window1.get());
  ToggleOverview();

  // Tap on |window2| to activate it and exit overview.
  GetEventGenerator()->GestureTapAt(
      GetTransformedTargetBounds(window2.get()).CenterPoint());
  EXPECT_EQ(
      1, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));

  // Click on |window2| to activate it and exit overview.
  wm::ActivateWindow(window1.get());
  ToggleOverview();
  ClickWindow(window2.get());
  EXPECT_EQ(
      2, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));

  // Focus `window2` using the arrow keys. Activate it (and exit overview) by
  // pressing the return key.
  wm::ActivateWindow(window1.get());
  ToggleOverview();
  PressAndReleaseKey(ui::VKEY_TAB);
  PressAndReleaseKey(ui::VKEY_TAB);
  ASSERT_EQ(static_cast<OverviewItem*>(GetOverviewItemForWindow(window2.get()))
                ->overview_item_view(),
            GetFocusedView());
  PressAndReleaseKey(ui::VKEY_RETURN);
  EXPECT_EQ(
      3, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));
}

// Tests that the user action WindowSelector_ActiveWindowChanged is not
// recorded when the mouse/touchscreen/keyboard are used to select the
// already-active window from overview mode. Also verifies that entering and
// exiting overview without selecting a window does not record the action.
TEST_P(OverviewSessionTest, ActiveWindowChangedUserActionNotRecorded) {
  base::UserActionTester user_action_tester;
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  wm::ActivateWindow(window1.get());
  ToggleOverview();

  // Tap on |window1| to exit overview.
  GetEventGenerator()->GestureTapAt(
      GetTransformedTargetBounds(window1.get()).CenterPoint());
  EXPECT_EQ(
      0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));

  // |window1| remains active. Click on it to exit overview.
  ASSERT_EQ(window1.get(), window_util::GetFocusedWindow());
  ToggleOverview();
  ClickWindow(window1.get());
  EXPECT_EQ(
      0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));

  // |window1| remains active. Select using the keyboard.
  ASSERT_EQ(window1.get(), window_util::GetFocusedWindow());
  ToggleOverview();
  PressAndReleaseKey(ui::VKEY_TAB);
  ASSERT_EQ(static_cast<OverviewItem*>(GetOverviewItemForWindow(window1.get()))
                ->overview_item_view(),
            GetFocusedView());
  PressAndReleaseKey(ui::VKEY_RETURN);
  EXPECT_EQ(
      0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));

  // Entering and exiting overview without user input should not record
  // the action.
  ToggleOverview();
  ToggleOverview();
  EXPECT_EQ(
      0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));
}

// Tests that the user action WindowSelector_ActiveWindowChanged is not
// recorded when overview mode exits as a result of closing its only window.
TEST_P(OverviewSessionTest, ActiveWindowChangedUserActionWindowClose) {
  base::UserActionTester user_action_tester;
  std::unique_ptr<views::Widget> widget(CreateTestWidget(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, nullptr,
      desks_util::GetActiveDeskContainerId(), gfx::Rect(400, 400)));

  ToggleOverview();
  aura::Window* window = widget->GetNativeWindow();
  const gfx::Point point = GetCloseButton(GetOverviewItemForWindow(window))
                               ->GetBoundsInScreen()
                               .CenterPoint();
  ASSERT_FALSE(widget->IsClosed());
  GetEventGenerator()->set_current_screen_location(point);
  GetEventGenerator()->ClickLeftButton();
  ASSERT_TRUE(widget->IsClosed());
  EXPECT_EQ(
      0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));
}

// Tests that we do not crash and overview mode remains engaged if the desktop
// is tapped while a finger is already down over a window.
TEST_P(OverviewSessionTest, NoCrashWithDesktopTap) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(gfx::Rect(200, 300, 250, 450)));

  ToggleOverview();

  const gfx::Rect bounds = GetTransformedBoundsInRootWindow(window.get());
  GetEventGenerator()->set_current_screen_location(bounds.CenterPoint());

  // Press down on the window.
  const int kTouchId = 19;
  GetEventGenerator()->PressTouchId(kTouchId);

  // Tap on the desktop, which should not cause a crash. Overview mode should
  // remain engaged.
  GetEventGenerator()->GestureTapAt(GetGridBounds().CenterPoint());
  EXPECT_TRUE(InOverviewSession());

  GetEventGenerator()->ReleaseTouchId(kTouchId);
}

// Tests that we do not crash and a window is selected when appropriate when
// we click on a window during touch.
TEST_P(OverviewSessionTest, ClickOnWindowDuringTouch) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  wm::ActivateWindow(window2.get());
  EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  ToggleOverview();

  gfx::Rect window1_bounds = GetTransformedBoundsInRootWindow(window1.get());
  GetEventGenerator()->set_current_screen_location(
      window1_bounds.CenterPoint());

  // Clicking on |window2| while touching on |window1| should not cause a
  // crash, it should do nothing since overview only handles one click or touch
  // at a time.
  const int kTouchId = 19;
  GetEventGenerator()->PressTouchId(kTouchId);
  GetEventGenerator()->MoveMouseToCenterOf(window2.get());
  GetEventGenerator()->ClickLeftButton();
  EXPECT_TRUE(InOverviewSession());
  EXPECT_FALSE(wm::IsActiveWindow(window2.get()));

  // Clicking on |window1| while touching on |window1| should not cause
  // a crash, overview mode should be disengaged, and |window1| should
  // be active.
  GetEventGenerator()->MoveMouseToCenterOf(window1.get());
  GetEventGenerator()->ClickLeftButton();
  EXPECT_FALSE(InOverviewSession());
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
  GetEventGenerator()->ReleaseTouchId(kTouchId);
}

// Tests that a window does not receive located events when in overview mode.
TEST_P(OverviewSessionTest, WindowDoesNotReceiveEvents) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(400, 400)));
  const gfx::Point point1 = window->bounds().CenterPoint();
  ui::MouseEvent event1(ui::EventType::kMousePressed, point1, point1,
                        ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);

  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  ui::EventTarget* root_target = root_window;
  ui::EventTargeter* targeter =
      root_window->GetHost()->dispatcher()->GetDefaultEventTargeter();

  // The event should target the window because we are still not in overview
  // mode.
  EXPECT_EQ(window.get(), targeter->FindTargetForEvent(root_target, &event1));

  ToggleOverview();

  // The bounds have changed, take that into account.
  const gfx::Point point2 =
      GetTransformedBoundsInRootWindow(window.get()).CenterPoint();
  ui::MouseEvent event2(ui::EventType::kMousePressed, point2, point2,
                        ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);

  // Now the transparent window should be intercepting this event.
  EXPECT_NE(window.get(), targeter->FindTargetForEvent(root_target, &event2));
}

// Tests that clicking on the close button effectively closes the window.
TEST_P(OverviewSessionTest, CloseButton) {
  std::unique_ptr<views::Widget> widget(
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET));
  std::unique_ptr<views::Widget> minimized_widget(
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET));
  minimized_widget->Minimize();

  ToggleOverview();
  aura::Window* window = widget->GetNativeWindow();
  const gfx::Point point = GetCloseButton(GetOverviewItemForWindow(window))
                               ->GetBoundsInScreen()
                               .CenterPoint();
  GetEventGenerator()->set_current_screen_location(point);

  EXPECT_FALSE(widget->IsClosed());
  GetEventGenerator()->ClickLeftButton();
  EXPECT_TRUE(widget->IsClosed());
  ASSERT_TRUE(InOverviewSession());

  aura::Window* minimized_window = minimized_widget->GetNativeWindow();
  WindowPreviewView* preview_view =
      GetPreviewView(GetOverviewItemForWindow(minimized_window));
  EXPECT_TRUE(preview_view);
  const gfx::Point point2 =
      GetCloseButton(GetOverviewItemForWindow(minimized_window))
          ->GetBoundsInScreen()
          .CenterPoint();
  GetEventGenerator()->MoveMouseTo(point2);
  EXPECT_FALSE(minimized_widget->IsClosed());

  GetEventGenerator()->ClickLeftButton();
  EXPECT_TRUE(minimized_widget->IsClosed());

  // All minimized windows are closed, so it should exit overview mode.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(InOverviewSession());
}

// Tests that the shadow disappears before the close animation starts.
// Regression test for https://crbug.com/981509.
TEST_P(OverviewSessionTest, CloseAnimationShadow) {
  // Give us some time to check if the shadow has disappeared.
  ScopedOverviewTransformWindow::SetImmediateCloseForTests(/*immediate=*/false);
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  std::unique_ptr<views::Widget> widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);

  ToggleOverview();
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kEnterAnimationComplete);
  // Click the close button.
  auto* item = GetOverviewItemForWindow(widget->GetNativeWindow());
  const gfx::Point point =
      GetCloseButton(item)->GetBoundsInScreen().CenterPoint();
  GetEventGenerator()->set_current_screen_location(point);
  GetEventGenerator()->ClickLeftButton();
  ASSERT_FALSE(widget->IsClosed());
  ASSERT_TRUE(InOverviewSession());

  // The shadow bounds are empty, which means its not visible.
  EXPECT_EQ(gfx::Rect(), GetShadowBounds(item));
}

// Tests minimizing/unminimizing in overview mode.
TEST_P(OverviewSessionTest, MinimizeUnminimize) {
  std::unique_ptr<views::Widget> widget(
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET));
  aura::Window* window = widget->GetNativeWindow();

  ToggleOverview();
  EXPECT_FALSE(GetPreviewView(GetOverviewItemForWindow(window)));

  widget->Minimize();
  EXPECT_TRUE(widget->IsMinimized());
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(GetPreviewView(GetOverviewItemForWindow(window)));

  widget->Restore();
  EXPECT_FALSE(widget->IsMinimized());
  EXPECT_FALSE(GetPreviewView(GetOverviewItemForWindow(window)));
  EXPECT_TRUE(InOverviewSession());
}

// Tests that clicking on the close button on a secondary display effectively
// closes the window.
TEST_P(OverviewSessionTest, CloseButtonOnMultipleDisplay) {
  UpdateDisplay("600x400,600x400");

  // We need a widget for the close button to work because windows are closed
  // via the widget. We also use the widget to determine if the window has been
  // closed or not. Parent the window to a window in a non-primary root window.
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(gfx::Rect(650, 300, 250, 450)));
  std::unique_ptr<views::Widget> widget(
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET));
  widget->SetBounds(gfx::Rect(650, 0, 400, 400));
  aura::Window* window2 = widget->GetNativeWindow();
  window2->SetProperty(aura::client::kTopViewInset,
                       kWindowMiniViewHeaderHeight);
  views::Widget::ReparentNativeView(window2, window->parent());
  ASSERT_EQ(Shell::GetAllRootWindows()[1], window2->GetRootWindow());

  ToggleOverview();
  gfx::Rect bounds = GetTransformedBoundsInRootWindow(window2);
  gfx::Point point(bounds.right() - 15, bounds.y() + 15);
  ui::test::EventGenerator event_generator(window2->GetRootWindow(), point);

  EXPECT_FALSE(widget->IsClosed());
  event_generator.ClickLeftButton();
  EXPECT_TRUE(widget->IsClosed());
}

// Test that we mirror the the correct widgets when dragging across displays.
TEST_P(OverviewSessionTest, DraggingOnMultipleDisplay) {
  UpdateDisplay("600x400,600x400");

  // Create one normal window and one minimzied window.
  auto normal_window = CreateAppWindow();
  auto minimized_window = CreateAppWindow();
  WMEvent minimize_event(WM_EVENT_MINIMIZE);
  WindowState::Get(minimized_window.get())->OnWMEvent(&minimize_event);

  ToggleOverview();
  auto* generator = GetEventGenerator();
  OverviewItem* normal_item =
      static_cast<OverviewItem*>(GetOverviewItemForWindow(normal_window.get()));
  OverviewItem* minimized_item = static_cast<OverviewItem*>(
      GetOverviewItemForWindow(minimized_window.get()));

  // Start dragging the normal window. We mirror both the overview item widget
  // and the normal window.
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(normal_item->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  generator->MoveMouseBy(20, 20);
  EXPECT_TRUE(normal_item->item_mirror_for_dragging_for_testing());
  EXPECT_TRUE(normal_item->window_mirror_for_dragging_for_testing());
  EXPECT_FALSE(minimized_item->item_mirror_for_dragging_for_testing());
  EXPECT_FALSE(minimized_item->window_mirror_for_dragging_for_testing());

  // Start dragging the minimzed window. We don't mirror the original window,
  // since the overview item widget already contains a mirror.
  generator->ReleaseLeftButton();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(minimized_item->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  generator->MoveMouseBy(20, 20);
  EXPECT_FALSE(normal_item->item_mirror_for_dragging_for_testing());
  EXPECT_FALSE(normal_item->window_mirror_for_dragging_for_testing());
  EXPECT_TRUE(minimized_item->item_mirror_for_dragging_for_testing());
  EXPECT_FALSE(minimized_item->window_mirror_for_dragging_for_testing());
}

// Tests that dragging an overview item with multiple displays and then exiting
// overview does not result in a u-a-f. Regression test for b/293867778.
TEST_P(OverviewSessionTest, ExitOverviewWhileDraggingOnMultipleDisplay) {
  UpdateDisplay("600x400,600x400");

  auto window = CreateAppWindow();

  ToggleOverview();
  auto* generator = GetEventGenerator();
  auto* item = GetOverviewItemForWindow(window.get());
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(item->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  generator->MoveMouseBy(20, 20);

  // Exit overview without completing the drag.
  ToggleOverview();
}

// Tests entering overview mode with two windows and selecting one.
TEST_P(OverviewSessionTest, FullscreenWindow) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  wm::ActivateWindow(window1.get());

  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window1.get())->OnWMEvent(&toggle_fullscreen_event);
  EXPECT_TRUE(WindowState::Get(window1.get())->IsFullscreen());

  // Enter overview and select the fullscreen window.
  ToggleOverview();
  ClickWindow(window1.get());
  ASSERT_FALSE(InOverviewSession());
  EXPECT_TRUE(WindowState::Get(window1.get())->IsFullscreen());

  // Entering overview and selecting another window, the previous window remains
  // fullscreen.
  ToggleOverview();
  ClickWindow(window2.get());
  EXPECT_FALSE(InOverviewSession());
  EXPECT_TRUE(WindowState::Get(window1.get())->IsFullscreen());
}

// Tests entering overview mode with maximized window.
TEST_P(OverviewSessionTest, MaximizedWindow) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  wm::ActivateWindow(window1.get());

  const WMEvent maximize_event(WM_EVENT_MAXIMIZE);
  WindowState::Get(window1.get())->OnWMEvent(&maximize_event);
  EXPECT_TRUE(WindowState::Get(window1.get())->IsMaximized());

  // Enter overview and select the maximized window.
  ToggleOverview();
  ClickWindow(window1.get());
  ASSERT_FALSE(InOverviewSession());
  EXPECT_TRUE(WindowState::Get(window1.get())->IsMaximized());

  ToggleOverview();
  ClickWindow(window2.get());
  EXPECT_FALSE(InOverviewSession());
  EXPECT_TRUE(WindowState::Get(window1.get())->IsMaximized());
}

// Tests the animation histograms when entering and exiting overview with a
// maximized and fullscreen window.
#if defined(NDEBUG) && !defined(ADDRESS_SANITIZER) &&         \
    !defined(LEAK_SANITIZER) && !defined(THREAD_SANITIZER) && \
    !defined(MEMORY_SANITIZER)
TEST_P(OverviewSessionTest, MaximizedFullscreenHistograms) {
  std::unique_ptr<aura::Window> maximized_window(CreateTestWindow());
  std::unique_ptr<aura::Window> fullscreen_window(CreateTestWindow());

  const WMEvent maximize_event(WM_EVENT_MAXIMIZE);
  WindowState::Get(maximized_window.get())->OnWMEvent(&maximize_event);
  ASSERT_TRUE(WindowState::Get(maximized_window.get())->IsMaximized());

  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(fullscreen_window.get())
      ->OnWMEvent(&toggle_fullscreen_event);
  ASSERT_TRUE(WindowState::Get(fullscreen_window.get())->IsFullscreen());

  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);

  // Enter and exit overview with the maximized window activated.
  wm::ActivateWindow(maximized_window.get());
  ToggleOverview();
  WaitForOverviewEnterAnimation();
  CheckOverviewEnterExitHistogram("MaximizedWindowEnter1", {0, 1, 0, 0, 0},
                                  {0, 0, 0, 0, 0});
  ToggleOverview();
  WaitForOverviewExitAnimation();
  CheckOverviewEnterExitHistogram("MaximizedWindowExit1", {0, 1, 0, 0, 0},
                                  {0, 1, 0, 0, 0});

  // Enter and exit overview with the fullscreen window activated.
  wm::ActivateWindow(fullscreen_window.get());
  ToggleOverview();
  WaitForOverviewEnterAnimation();
  CheckOverviewEnterExitHistogram("FullscreenWindowEnter1", {0, 2, 0, 0, 0},
                                  {0, 1, 0, 0, 0});
  ToggleOverview();
  WaitForOverviewExitAnimation();
  CheckOverviewEnterExitHistogram("FullscreenWindowExit1", {0, 2, 0, 0, 0},
                                  {0, 2, 0, 0, 0});
}
#endif

// TODO(crbug.com/1493835): Re-enable this test. Disabled because of flakiness.
TEST_P(OverviewSessionTest, DISABLED_TabletModeHistograms) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);

  EnterTabletMode();
  std::unique_ptr<aura::Window> window1(CreateTestWindow());

  // Enter overview with the window maximized.
  ToggleOverview();
  WaitForOverviewEnterAnimation();
  CheckOverviewEnterExitHistogram("MaximizedWindowTabletEnter", {0, 0, 1, 0, 0},
                                  {0, 0, 0, 0, 0});

  ToggleOverview();
  WaitForOverviewExitAnimation();
  CheckOverviewEnterExitHistogram("MaximizedWindowTabletExit", {0, 0, 1, 0, 0},
                                  {0, 0, 1, 0, 0});

  WindowState::Get(window1.get())->Minimize();
  ToggleOverview();
  WaitForOverviewEnterAnimation();
  CheckOverviewEnterExitHistogram("MinimizedWindowTabletEnter", {0, 0, 1, 1, 0},
                                  {0, 0, 1, 0, 0});

  ToggleOverview();
  WaitForOverviewExitAnimation();
  CheckOverviewEnterExitHistogram("MinimizedWindowTabletExit", {0, 0, 1, 1, 0},
                                  {0, 0, 1, 1, 0});
}

// Tests that entering overview when a fullscreen window is active in maximized
// mode correctly applies the transformations to the window and correctly
// updates the window bounds on exiting overview mode: http://crbug.com/401664.
// TODO(crbug.com/41496866): Fix flaky test.
TEST_P(OverviewSessionTest, DISABLED_FullscreenWindowTabletMode) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);

  UpdateDisplay("800x600");
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EnterTabletMode();
  gfx::Rect normal_window_bounds(window1->bounds());
  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window1.get())->OnWMEvent(&toggle_fullscreen_event);

  // Finish fullscreen state change animation since it is irrelevant.
  window1->layer()->GetAnimator()->StopAnimating();

  gfx::Rect fullscreen_window_bounds(window1->bounds());
  EXPECT_NE(normal_window_bounds, fullscreen_window_bounds);
  EXPECT_EQ(fullscreen_window_bounds, window2->GetTargetBounds());

  const gfx::Rect fullscreen(800, 600);
  const int shelf_inset = 600 - ShelfConfig::Get()->shelf_size();
  const gfx::Rect normal_work_area(800, shelf_inset);
  display::Screen* screen = display::Screen::GetScreen();
  EXPECT_EQ(gfx::Rect(800, 600),
            screen->GetDisplayNearestWindow(window1.get()).work_area());
  ToggleOverview();
  WaitForOverviewEnterAnimation();
  EXPECT_EQ(fullscreen,
            screen->GetDisplayNearestWindow(window1.get()).work_area());
  CheckOverviewEnterExitHistogram("FullscreenWindowTabletEnter1",
                                  {0, 0, 1, 0, 0}, {0, 0, 0, 0, 0});

  // Window 2 would normally resize to normal window bounds on showing the shelf
  // for overview but this is deferred until overview is exited.
  EXPECT_EQ(fullscreen_window_bounds, window2->GetTargetBounds());
  EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get()));
  ToggleOverview();
  WaitForOverviewExitAnimation();
  EXPECT_EQ(fullscreen,
            screen->GetDisplayNearestWindow(window1.get()).work_area());
  // Since the fullscreen window is still active, window2 will still have the
  // larger bounds.
  EXPECT_EQ(fullscreen_window_bounds, window2->GetTargetBounds());
  CheckOverviewEnterExitHistogram("FullscreenWindowTabletExit1",
                                  {0, 0, 1, 0, 0}, {0, 0, 1, 0, 0});

  // Enter overview again and select window 2. Selecting window 2 should show
  // the shelf bringing window2 back to the normal bounds.
  ToggleOverview();
  WaitForOverviewEnterAnimation();
  CheckOverviewEnterExitHistogram("FullscreenWindowTabletEnter2",
                                  {0, 0, 2, 0, 0}, {0, 0, 1, 0, 0});

  ClickWindow(window2.get());
  WaitForOverviewExitAnimation();
  // Selecting non fullscreen window should set the work area back to normal.
  EXPECT_EQ(normal_work_area,
            screen->GetDisplayNearestWindow(window1.get()).work_area());
  EXPECT_EQ(normal_window_bounds, window2->GetTargetBounds());
  CheckOverviewEnterExitHistogram("FullscreenWindowTabletExit2",
                                  {0, 0, 2, 0, 0}, {0, 0, 2, 0, 0});

  ToggleOverview();
  WaitForOverviewEnterAnimation();
  CheckOverviewEnterExitHistogram("FullscreenWindowTabletEnter3",
                                  {0, 0, 3, 0, 0}, {0, 0, 2, 0, 0});
  EXPECT_EQ(normal_work_area,
            screen->GetDisplayNearestWindow(window1.get()).work_area());
  ClickWindow(window1.get());
  WaitForOverviewExitAnimation();
  // Selecting fullscreen. The work area should be updated to fullscreen as
  // well.
  EXPECT_EQ(fullscreen,
            screen->GetDisplayNearestWindow(window1.get()).work_area());
  CheckOverviewEnterExitHistogram("FullscreenWindowTabletExit3",
                                  {0, 0, 3, 0, 0}, {0, 0, 3, 0, 0});
}

// Tests that when disabling ChromeVox, desks widget bounds on overview mode
// should be updated. Desks widget will be moved to the top of the screen.
TEST_P(OverviewSessionTest, DesksWidgetBoundsChangeWhenDisableChromeVox) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();

  AccessibilityController* accessibility_controller =
      Shell::Get()->accessibility_controller();

  // Enable ChromeVox.
  const int kAccessibilityPanelHeight = 45;
  // ChromeVox layout manager relies on the widget to validate ChromaVox panel's
  // exist. Check AccessibilityPanelLayoutManager::SetPanelBounds.
  std::unique_ptr<views::Widget> widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
                       nullptr, kShellWindowId_AccessibilityPanelContainer);
  SetAccessibilityPanelHeight(kAccessibilityPanelHeight);
  accessibility_controller->SetSpokenFeedbackEnabled(true,
                                                     A11Y_NOTIFICATION_NONE);
  // Enable overview mode.
  ToggleOverview();

  const views::Widget* desks_widget =
      GetOverviewSession()->grid_list()[0].get()->desks_widget();

  const gfx::Rect desks_widget_bounds = desks_widget->GetWindowBoundsInScreen();
  // Desks widget should lay out right below ChromeVox panel.
  EXPECT_EQ(desks_widget_bounds.y(), kAccessibilityPanelHeight);

  // Disable ChromeVox panel.
  accessibility_controller->SetSpokenFeedbackEnabled(false,
                                                     A11Y_NOTIFICATION_NONE);
  SetAccessibilityPanelHeight(0);

  const gfx::Rect desks_widget_bounds_after_disable_chromeVox =
      desks_widget->GetWindowBoundsInScreen();
  // Desks widget should be moved to the top of the screen after
  // disabling ChromeVox panel.
  EXPECT_EQ(desks_widget_bounds_after_disable_chromeVox.y(), 0);

  EXPECT_NE(desks_widget_bounds, desks_widget_bounds_after_disable_chromeVox);
}

TEST_P(OverviewSessionTest, SkipOverviewWindow) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  window2->SetProperty(kHideInOverviewKey, true);

  // Enter overview.
  ToggleOverview();
  EXPECT_TRUE(window1->IsVisible());
  EXPECT_FALSE(window2->IsVisible());

  // Exit overview.
  ToggleOverview();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(window1->IsVisible());
  EXPECT_TRUE(window2->IsVisible());
}

// Tests that a minimized window's visibility and layer visibility
// stay invisible (A minimized window is cloned during overview).
TEST_P(OverviewSessionTest, MinimizedWindowState) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  WindowState::Get(window1.get())->Minimize();
  EXPECT_FALSE(window1->IsVisible());
  EXPECT_FALSE(window1->layer()->GetTargetVisibility());

  ToggleOverview();
  EXPECT_FALSE(window1->IsVisible());
  EXPECT_FALSE(window1->layer()->GetTargetVisibility());

  ToggleOverview();
  EXPECT_FALSE(window1->IsVisible());
  EXPECT_FALSE(window1->layer()->GetTargetVisibility());
}

// Tests that a bounds change during overview is corrected for.
TEST_P(OverviewSessionTest, BoundsChangeDuringOverview) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(400, 400)));
  // Use overview headers above the window in this test.
  window->SetProperty(aura::client::kTopViewInset, 0);
  ToggleOverview();
  gfx::Rect overview_bounds = GetTransformedTargetBounds(window.get());
  window->SetBounds(gfx::Rect(200, 0, 200, 200));
  gfx::Rect new_overview_bounds = GetTransformedTargetBounds(window.get());
  EXPECT_EQ(overview_bounds, new_overview_bounds);
  ToggleOverview();
}

// Tests that a change to the |kTopViewInset| window property during overview is
// corrected for.
TEST_P(OverviewSessionTest, TopViewInsetChangeDuringOverview) {
  std::unique_ptr<aura::Window> window = CreateTestWindow(gfx::Rect(400, 400));
  window->SetProperty(aura::client::kTopViewInset, 32);
  ToggleOverview();
  gfx::Rect overview_bounds = GetTransformedTargetBounds(window.get());
  window->SetProperty(aura::client::kTopViewInset, 0);
  gfx::Rect new_overview_bounds = GetTransformedTargetBounds(window.get());
  EXPECT_NE(overview_bounds, new_overview_bounds);
  ToggleOverview();
}

// Tests that a newly created window aborts overview.
TEST_P(OverviewSessionTest, NewWindowCancelsOverview) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  // A window being created should exit overview mode.
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  EXPECT_FALSE(InOverviewSession());
}

// Tests that a window activation exits overview mode.
TEST_P(OverviewSessionTest, ActivationCancelsOverview) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  window2->Focus();
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  // A window being activated should exit overview mode.
  window1->Focus();
  EXPECT_FALSE(InOverviewSession());

  // window1 should be focused after exiting even though window2 was focused on
  // entering overview because we exited due to an activation.
  EXPECT_EQ(window1.get(), window_util::GetFocusedWindow());
}

// Tests that if an overview item is dragged, the activation of the
// corresponding window does not cancel overview.
TEST_P(OverviewSessionTest, ActivateDraggedOverviewWindowNotCancelOverview) {
  UpdateDisplay("800x600");
  EnterTabletMode();
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  ToggleOverview();
  auto* item = GetOverviewItemForWindow(window.get());
  gfx::PointF drag_point = item->target_bounds().CenterPoint();
  GetOverviewSession()->InitiateDrag(item, drag_point,
                                     /*is_touch_dragging=*/false,
                                     /*event_source_item=*/item);
  drag_point.Offset(5.f, 0.f);
  GetOverviewSession()->Drag(item, drag_point);
  wm::ActivateWindow(window.get());
  EXPECT_TRUE(InOverviewSession());
}

// Tests that if an overview item is dragged, the activation of the window
// corresponding to another overview item does not cancel overview.
TEST_P(OverviewSessionTest,
       ActivateAnotherOverviewWindowDuringOverviewDragNotCancelOverview) {
  UpdateDisplay("800x600");
  EnterTabletMode();
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  ToggleOverview();
  OverviewItemBase* item1 = GetOverviewItemForWindow(window1.get());
  gfx::PointF drag_point = item1->target_bounds().CenterPoint();
  GetOverviewSession()->InitiateDrag(item1, drag_point,
                                     /*is_touch_dragging=*/false,
                                     /*event_source_item=*/item1);
  drag_point.Offset(5.f, 0.f);
  GetOverviewSession()->Drag(item1, drag_point);
  wm::ActivateWindow(window2.get());
  EXPECT_TRUE(InOverviewSession());
}

// Tests that if an overview item is dragged, the activation of a window
// excluded from overview does not cancel overview.
TEST_P(OverviewSessionTest,
       ActivateWindowExcludedFromOverviewDuringOverviewDragNotCancelOverview) {
  UpdateDisplay("800x600");
  EnterTabletMode();
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(
      CreateTestWindow(gfx::Rect(), aura::client::WINDOW_TYPE_POPUP));
  EXPECT_TRUE(window_util::ShouldExcludeForOverview(window2.get()));
  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  gfx::PointF drag_point = item1->target_bounds().CenterPoint();
  GetOverviewSession()->InitiateDrag(item1, drag_point,
                                     /*is_touch_dragging=*/false,
                                     /*event_source_item=*/item1);
  drag_point.Offset(5.f, 0.f);
  GetOverviewSession()->Drag(item1, drag_point);
  wm::ActivateWindow(window2.get());
  EXPECT_TRUE(InOverviewSession());
}

// Tests that exiting overview mode without selecting a window restores focus
// to the previously focused window.
TEST_P(OverviewSessionTest, CancelRestoresFocus) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  wm::ActivateWindow(window.get());
  EXPECT_EQ(window.get(), window_util::GetFocusedWindow());

  // In overview mode, the overview focus window should be focused.
  ToggleOverview();
  EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(),
            window_util::GetFocusedWindow());

  // If canceling overview mode, focus should be restored.
  ToggleOverview();
  EXPECT_EQ(window.get(), window_util::GetFocusedWindow());
}

// Tests that overview mode is exited if the last remaining window is destroyed.
TEST_P(OverviewSessionTest, LastWindowDestroyed) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  ToggleOverview();

  window1.reset();
  window2.reset();
  EXPECT_FALSE(InOverviewSession());
}

// Tests that entering overview mode restores a window to its original
// target location.
TEST_P(OverviewSessionTest, QuickReentryRestoresInitialTransform) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(400, 400)));
  gfx::Rect initial_bounds = GetTransformedBounds(window.get());
  ToggleOverview();
  // Quickly exit and reenter overview mode. The window should still be
  // animating when we reenter. We cannot short circuit animations for this but
  // we also don't have to wait for them to complete.
  {
    ui::ScopedAnimationDurationScaleMode test_duration_mode(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
    ToggleOverview();
    ToggleOverview();
  }
  EXPECT_NE(initial_bounds, GetTransformedTargetBounds(window.get()));
  ToggleOverview();
  EXPECT_FALSE(InOverviewSession());
  EXPECT_EQ(initial_bounds, GetTransformedTargetBounds(window.get()));
}

// Tests that windows with modal child windows are transformed with the modal
// child even though not activatable themselves.
TEST_P(OverviewSessionTest, ModalChild) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> child(CreateTestWindow(bounds));
  child->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kWindow);
  ::wm::AddTransientChild(window.get(), child.get());
  EXPECT_EQ(window->parent(), child->parent());
  ToggleOverview();
  EXPECT_TRUE(window->IsVisible());
  EXPECT_TRUE(child->IsVisible());
  EXPECT_EQ(GetTransformedTargetBounds(child.get()),
            GetTransformedTargetBounds(window.get()));
  ToggleOverview();
}

// Tests that clicking a modal window's parent activates the modal window in
// overview.
TEST_P(OverviewSessionTest, ClickModalWindowParent) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(180, 180)));
  std::unique_ptr<aura::Window> child(
      CreateTestWindow(gfx::Rect(200, 0, 180, 180)));
  child->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kWindow);
  ::wm::AddTransientChild(window.get(), child.get());
  EXPECT_FALSE(WindowsOverlapping(window.get(), child.get()));
  EXPECT_EQ(window->parent(), child->parent());
  ToggleOverview();
  // Given that their relative positions are preserved, the windows should still
  // not overlap.
  EXPECT_FALSE(WindowsOverlapping(window.get(), child.get()));
  ClickWindow(window.get());
  EXPECT_FALSE(InOverviewSession());

  // Clicking on window1 should activate child1.
  EXPECT_TRUE(wm::IsActiveWindow(child.get()));
}

// Verifies bubble transient windows hide in Overview, reappear on Overview
// exit.
TEST_P(OverviewSessionTest, HideBubbleTransient) {
  std::unique_ptr<aura::Window> window(
      CreateAppWindow(gfx::Rect(0, 0, 300, 300)));

  // Create a bubble widget that's anchored to frame.
  auto bubble_delegate = std::make_unique<views::BubbleDialogDelegateView>(
      NonClientFrameViewAsh::Get(window.get()), views::BubbleBorder::TOP_RIGHT);

  // The line below is essential to make sure that the bubble doesn't get closed
  // when entering overview.
  bubble_delegate->set_close_on_deactivate(false);
  bubble_delegate->set_parent_window(window.get());
  views::Widget* bubble_widget(views::BubbleDialogDelegateView::CreateBubble(
      std::move(bubble_delegate)));
  aura::Window* bubble_window = bubble_widget->GetNativeWindow();
  ASSERT_TRUE(window_util::AsBubbleDialogDelegate(bubble_window));

  bubble_widget->Show();
  EXPECT_TRUE(wm::HasTransientAncestor(bubble_window, window.get()));

  // Hides bubble transient windows on entering Overview mode.
  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());
  EXPECT_FALSE(bubble_window->IsVisible());

  // Re-shows bubble transient windows on exiting Overview mode.
  ToggleOverview();
  ASSERT_FALSE(IsInOverviewSession());
  EXPECT_TRUE(bubble_window->IsVisible());
}

// Tests that windows remain on the display they are currently on in overview
// mode, and that the close buttons are on matching displays.
TEST_P(OverviewSessionTest, MultipleDisplays) {
  UpdateDisplay("600x400,600x400");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  gfx::Rect bounds1(0, 0, 400, 400);
  gfx::Rect bounds2(650, 0, 400, 400);

  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds1));
  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds2));
  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds2));
  EXPECT_EQ(root_windows[0], window1->GetRootWindow());
  EXPECT_EQ(root_windows[0], window2->GetRootWindow());
  EXPECT_EQ(root_windows[1], window3->GetRootWindow());
  EXPECT_EQ(root_windows[1], window4->GetRootWindow());

  // In overview mode, each window remains in the same root window.
  ToggleOverview();
  EXPECT_EQ(root_windows[0], window1->GetRootWindow());
  EXPECT_EQ(root_windows[0], window2->GetRootWindow());
  EXPECT_EQ(root_windows[1], window3->GetRootWindow());
  EXPECT_EQ(root_windows[1], window4->GetRootWindow());

  // Window indices are based on top-down order. The reverse of our creation.
  CheckWindowAndCloseButtonInScreen(window1.get(),
                                    GetOverviewItemForWindow(window1.get()));
  CheckWindowAndCloseButtonInScreen(window2.get(),
                                    GetOverviewItemForWindow(window2.get()));
  CheckWindowAndCloseButtonInScreen(window3.get(),
                                    GetOverviewItemForWindow(window3.get()));
  CheckWindowAndCloseButtonInScreen(window4.get(),
                                    GetOverviewItemForWindow(window4.get()));
}

// Tests shutting down during overview.
TEST_P(OverviewSessionTest, Shutdown) {
  // These windows will be deleted when the test exits and the Shell instance
  // is shut down.
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  ToggleOverview();
}

// Tests adding a display during overview.
TEST_P(OverviewSessionTest, AddDisplay) {
  UpdateDisplay("500x400");
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());
  UpdateDisplay("500x400,500x400");
  EXPECT_FALSE(InOverviewSession());
}

// Tests removing a display during overview.
TEST_P(OverviewSessionTest, RemoveDisplay) {
  UpdateDisplay("500x400,500x400");
  std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(100, 100)));
  std::unique_ptr<aura::Window> window2(
      CreateTestWindow(gfx::Rect(550, 0, 100, 100)));

  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  EXPECT_EQ(root_windows[0], window1->GetRootWindow());
  EXPECT_EQ(root_windows[1], window2->GetRootWindow());

  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());
  UpdateDisplay("500x400");
  EXPECT_FALSE(InOverviewSession());
}

// Tests removing a display during overview with NON_ZERO_DURATION animation.
TEST_P(OverviewSessionTest, RemoveDisplayWithAnimation) {
  UpdateDisplay("500x400,500x400");
  std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(100, 100)));
  std::unique_ptr<aura::Window> window2(
      CreateTestWindow(gfx::Rect(550, 0, 100, 100)));

  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  EXPECT_EQ(root_windows[0], window1->GetRootWindow());
  EXPECT_EQ(root_windows[1], window2->GetRootWindow());

  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  UpdateDisplay("500x400");
  EXPECT_FALSE(InOverviewSession());
}

// Tests that tab key does not cause crash if pressed just after overview
// session exits.
TEST_P(OverviewSessionTest, NoCrashOnTabAfterExit) {
  std::unique_ptr<aura::Window> window = CreateTestWindow();
  wm::ActivateWindow(window.get());

  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  ToggleOverview();
  PressAndReleaseKey(ui::VKEY_TAB);
  EXPECT_FALSE(InOverviewSession());
}

// Tests that tab key does not cause crash if pressed just after overview
// session exits, and a child window was active before session start.
TEST_P(OverviewSessionTest,
       NoCrashOnTabAfterExitWithChildWindowInitiallyFocused) {
  std::unique_ptr<aura::Window> window = CreateTestWindow();
  std::unique_ptr<aura::Window> child_window =
      ChildTestWindowBuilder(window.get()).Build();

  wm::ActivateWindow(child_window.get());

  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  ToggleOverview();
  PressAndReleaseKey(ui::VKEY_TAB);
  EXPECT_FALSE(InOverviewSession());
}

// Tests that tab key does not cause crash if pressed just after overview
// session exits when no windows existed before starting overview session.
TEST_P(OverviewSessionTest, NoCrashOnTabAfterExitWithNoWindows) {
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  ToggleOverview();
  PressAndReleaseKey(ui::VKEY_TAB);
  EXPECT_FALSE(InOverviewSession());
}

// Tests that dragging a window from overview creates a drop target on the same
// display.
TEST_P(OverviewSessionTest, DropTargetOnCorrectDisplayForDraggingFromOverview) {
  UpdateDisplay("600x500,600x500");
  EnterTabletMode();
  // DisplayConfigurationObserver enables mirror mode when tablet mode is
  // enabled. Disable mirror mode to test multiple displays.
  display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt);
  base::RunLoop().RunUntilIdle();

  const aura::Window::Windows root_windows = Shell::Get()->GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());

  std::unique_ptr<aura::Window> primary_screen_window =
      CreateTestWindow(gfx::Rect(0, 0, 600, 500));
  ASSERT_EQ(root_windows[0], primary_screen_window->GetRootWindow());
  std::unique_ptr<aura::Window> secondary_screen_window =
      CreateTestWindow(gfx::Rect(600, 0, 600, 500));
  ASSERT_EQ(root_windows[1], secondary_screen_window->GetRootWindow());

  ToggleOverview();
  auto* primary_screen_item =
      GetOverviewItemForWindow(primary_screen_window.get());
  auto* secondary_screen_item =
      GetOverviewItemForWindow(secondary_screen_window.get());

  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
  gfx::PointF drag_point = primary_screen_item->target_bounds().CenterPoint();
  GetOverviewSession()->InitiateDrag(primary_screen_item, drag_point,
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/primary_screen_item);
  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
  drag_point.Offset(5.f, 0.f);
  GetOverviewSession()->Drag(primary_screen_item, drag_point);
  EXPECT_FALSE(GetDropTarget(1));
  ASSERT_TRUE(GetDropTarget(0));
  EXPECT_EQ(root_windows[0], GetDropTarget(0)->root_window());
  GetOverviewSession()->CompleteDrag(primary_screen_item, drag_point);
  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
  drag_point = secondary_screen_item->target_bounds().CenterPoint();
  GetOverviewSession()->InitiateDrag(
      secondary_screen_item, drag_point,
      /*is_touch_dragging=*/true,
      /*event_source_item=*/secondary_screen_item);
  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
  drag_point.Offset(5.f, 0.f);
  GetOverviewSession()->Drag(secondary_screen_item, drag_point);
  EXPECT_FALSE(GetDropTarget(0));
  ASSERT_TRUE(GetDropTarget(1));
  EXPECT_EQ(root_windows[1], GetDropTarget(1)->root_window());
  GetOverviewSession()->CompleteDrag(secondary_screen_item, drag_point);
  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
}

// Tests that toggling overview on and off does not cancel drag.
TEST_P(OverviewSessionTest, DragDropInProgress) {
  auto* window_delegate =
      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
  window_delegate->set_window_component(HTCAPTION);
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
      window_delegate, -1, gfx::Rect(100, 100)));

  GetEventGenerator()->set_current_screen_location(
      window->GetBoundsInScreen().CenterPoint());
  GetEventGenerator()->PressLeftButton();
  GetEventGenerator()->MoveMouseBy(10, 10);
  EXPECT_EQ(gfx::Rect(10, 10, 100, 100), window->bounds());

  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  GetEventGenerator()->MoveMouseBy(10, 10);

  ToggleOverview();
  ASSERT_FALSE(InOverviewSession());

  GetEventGenerator()->MoveMouseBy(10, 10);
  GetEventGenerator()->ReleaseLeftButton();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(gfx::Rect(30, 30, 100, 100), window->bounds());
}

// Tests that toggling overview on removes any resize shadows that may have been
// present.
TEST_P(OverviewSessionTest, DragWindowShadow) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(100, 100)));
  wm::ActivateWindow(window.get());
  Shell::Get()->resize_shadow_controller()->ShowShadow(window.get(), HTTOP);

  ToggleOverview();
  ResizeShadow* shadow =
      Shell::Get()->resize_shadow_controller()->GetShadowForWindowForTest(
          window.get());
  EXPECT_FALSE(shadow);
}

// Test that a label is created under the window on entering overview mode.
TEST_P(OverviewSessionTest, CreateLabelUnderWindow) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(300, 500)));
  const std::u16string window_title = u"My window";
  window->SetTitle(window_title);
  ToggleOverview();
  auto* window_item = GetOverviewItemsForRoot(0).back().get();
  views::Label* label = GetLabelView(window_item);
  ASSERT_TRUE(label);

  // Verify the label matches the window title.
  EXPECT_EQ(window_title, label->GetText());

  // Update the window title and check that the label is updated, too.
  const std::u16string updated_title = u"Updated title";
  window->SetTitle(updated_title);
  EXPECT_EQ(updated_title, label->GetText());

  // Labels are located based on target_bounds, not the actual window item
  // bounds.
  gfx::RectF label_bounds(label->GetWidget()->GetWindowBoundsInScreen());
  EXPECT_EQ(label_bounds, window_item->target_bounds());
}

// Tests that overview updates the window positions if the display orientation
// changes.
TEST_P(OverviewSessionTest, DisplayOrientationChanged) {
  aura::Window* root_window = Shell::Get()->GetPrimaryRootWindow();
  UpdateDisplay("600x200");
  EXPECT_EQ(gfx::Rect(600, 200), root_window->bounds());
  std::vector<std::unique_ptr<aura::Window>> windows;
  for (int i = 0; i < 3; i++) {
    windows.push_back(
        std::unique_ptr<aura::Window>(CreateTestWindow(gfx::Rect(150, 150))));
  }

  ToggleOverview();
  for (const auto& window : windows) {
    EXPECT_TRUE(root_window->bounds().Contains(
        GetTransformedTargetBounds(window.get())));
  }

  // Rotate the display, windows should be repositioned to be within the screen
  // bounds.
  UpdateDisplay("600x200/r");
  EXPECT_EQ(gfx::Rect(200, 600), root_window->bounds());
  for (const auto& window : windows) {
    EXPECT_TRUE(root_window->bounds().Contains(
        GetTransformedTargetBounds(window.get())));
  }
}

TEST_P(OverviewSessionTest, AcceleratorInOverviewSession) {
  ToggleOverview();
  auto* accelerator_controller = Shell::Get()->accelerator_controller();
  auto* ewh = AcceleratorControllerImpl::TestApi(accelerator_controller)
                  .GetExitWarningHandler();
  ASSERT_TRUE(ewh);
  StubForTest(ewh);
  EXPECT_FALSE(IsUIShown(ewh));

  PressAndReleaseKey(ui::VKEY_Q, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
  EXPECT_TRUE(IsUIShown(ewh));
}

// Tests that overview session will exit when clicking on the empty area in
// overview.
TEST_P(OverviewSessionTest, ExitOverviewWhenClickingEmptyArea) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  ToggleOverview();
  OverviewController* overview_controller = GetOverviewController();
  ASSERT_TRUE(overview_controller->InOverviewSession());
  ASSERT_EQ(1u, GetOverviewSession()->grid_list().size());

  auto* overview_item = GetOverviewItemForWindow(window.get());
  EXPECT_TRUE(overview_item);
  const auto outside_point =
      gfx::ToRoundedPoint(
          overview_item->GetTransformedBounds().bottom_right()) +
      gfx::Vector2d(20, 20);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(outside_point);
  event_generator->ClickLeftButton();
  EXPECT_FALSE(overview_controller->InOverviewSession());
}

// Tests hitting the escape and back keys exits overview mode.
TEST_P(OverviewSessionTest, ExitOverviewWithKey) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  PressAndReleaseKey(ui::VKEY_ESCAPE);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  PressAndReleaseKey(ui::VKEY_BROWSER_BACK);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());

  // Tests that in tablet mode, if we snap the only overview window, we cannot
  // exit overview mode.
  EnterTabletMode();
  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  GetSplitViewController()->SnapWindow(window.get(), SnapPosition::kPrimary);
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  PressAndReleaseKey(ui::VKEY_ESCAPE);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  PressAndReleaseKey(ui::VKEY_BROWSER_BACK);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
}

// Regression test for clusterfuzz crash. https://crbug.com/920568
TEST_P(OverviewSessionTest, TypeThenPressEscapeTwice) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  ToggleOverview();

  // Type some characters.
  PressAndReleaseKey(ui::VKEY_A);
  PressAndReleaseKey(ui::VKEY_B);
  PressAndReleaseKey(ui::VKEY_C);
  EXPECT_TRUE(GetOverviewSession()->GetOverviewFocusWindow());

  // Pressing escape twice should not crash.
  PressAndReleaseKey(ui::VKEY_ESCAPE);
  PressAndReleaseKey(ui::VKEY_ESCAPE);
}

TEST_P(OverviewSessionTest, CancelOverviewOnMouseClick) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(gfx::Rect(10, 10, 100, 100)));
  // Move mouse to point in the background page. Sending an event here will pass
  // it to the WallpaperController in both regular and overview mode.
  GetEventGenerator()->MoveMouseTo(gfx::Point(0, 0));

  // Clicking on the background page while not in overview should not toggle
  // overview.
  GetEventGenerator()->ClickLeftButton();
  EXPECT_FALSE(InOverviewSession());

  // Switch to overview mode. Clicking should now exit overview mode.
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());
  // Choose a point that doesn't intersect with the window or the desks bar.
  const gfx::Point point_in_background_page = GetGridBounds().CenterPoint();
  GetEventGenerator()->MoveMouseTo(point_in_background_page);
  GetEventGenerator()->ClickLeftButton();
  EXPECT_FALSE(InOverviewSession());
}

// Tests tapping on the desktop itself to cancel overview mode.
TEST_P(OverviewSessionTest, CancelOverviewOnTap) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(gfx::Rect(10, 10, 100, 100)));

  // Tapping on the background page while not in overview should not toggle
  // overview.
  GetEventGenerator()->GestureTapAt(gfx::Point(0, 0));
  EXPECT_FALSE(InOverviewSession());

  // Switch to overview mode. Tapping should now exit overview mode.
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());
  // A point that doesn't intersect with the window nor the desks bar. This
  // causes events located at the point to be passed to WallpaperController, and
  // not the window.
  const gfx::Point point_in_background_page = GetGridBounds().CenterPoint();
  GetEventGenerator()->GestureTapAt(point_in_background_page);
  EXPECT_FALSE(InOverviewSession());
}

// Start dragging a window and activate overview mode. This test should not
// crash or DCHECK inside aura::Window::StackChildRelativeTo().
TEST_P(OverviewSessionTest, OverviewWhileDragging) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  std::unique_ptr<WindowResizer> resizer(CreateWindowResizer(
      window.get(), gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_MOUSE));
  ASSERT_TRUE(resizer.get());
  gfx::PointF location = resizer->GetInitialLocation();
  location.Offset(20, 20);
  resizer->Drag(location, 0);
  ToggleOverview();
  resizer->RevertDrag();
}

// Verify that the overview no windows indicator appears when entering overview
// mode with no windows.
TEST_P(OverviewSessionTest, NoWindowsIndicator) {
  UpdateDisplay("400x300,400x300");

  // Verify that by entering overview mode without windows, the no items
  // indicator appears.
  ToggleOverview();
  ASSERT_TRUE(GetOverviewSession());
  ASSERT_EQ(0u, GetOverviewItemsForRoot(0).size());
  for (auto& grid : GetOverviewSession()->grid_list())
    EXPECT_TRUE(grid->no_windows_widget());
}

// Verify that the overview no windows indicator position is as expected.
TEST_P(OverviewSessionTest, NoWindowsIndicatorPosition) {
  UpdateDisplay("400x300");

  ToggleOverview();
  ASSERT_TRUE(GetOverviewSession());

  RoundedLabelWidget* no_windows_widget =
      GetOverviewSession()->grid_list()[0]->no_windows_widget();
  ASSERT_TRUE(no_windows_widget);

  display::Screen* screen = display::Screen::GetScreen();

  // The expected y of the label will be the screen minus the shelf and desks
  // bar.
  auto get_expected_y = [&screen]() -> int {
    const int display_height = screen->GetPrimaryDisplay().bounds().height();
    const int grid_y = kDeskBarZeroStateHeight;
    int grid_height = display_height - ShelfConfig::Get()->shelf_size() -
                      kDeskBarZeroStateHeight;
    return grid_y + grid_height / 2;
  };

  // Verify that originally the label is in the center of the workspace. For
  // forest, the padding calculations are much more complicated and we need to
  // account for the birch bar, so we just check that the widget is roughly
  // centered vertically.
  gfx::Point no_windows_centerpoint =
      no_windows_widget->GetWindowBoundsInScreen().CenterPoint();
  if (features::IsForestFeatureEnabled()) {
    EXPECT_EQ(200, no_windows_centerpoint.x());
    EXPECT_GT(no_windows_centerpoint.y(), kDeskBarZeroStateHeight);
    EXPECT_LT(no_windows_centerpoint.y(),
              screen->GetPrimaryDisplay().bounds().height() -
                  ShelfConfig::Get()->shelf_size());
  } else {
    EXPECT_EQ(gfx::Point(200, get_expected_y()), no_windows_centerpoint);
  }

  // Verify that after rotating the display, the label is centered in the
  // workspace.
  const display::Display& display = screen->GetPrimaryDisplay();
  display_manager()->SetDisplayRotation(
      display.id(), display::Display::ROTATE_90,
      display::Display::RotationSource::ACTIVE);
  no_windows_centerpoint =
      no_windows_widget->GetWindowBoundsInScreen().CenterPoint();
  if (features::IsForestFeatureEnabled()) {
    EXPECT_EQ(150, no_windows_centerpoint.x());
    EXPECT_GT(no_windows_centerpoint.y(), kDeskBarZeroStateHeight);
    EXPECT_LT(no_windows_centerpoint.y(),
              screen->GetPrimaryDisplay().bounds().height() -
                  ShelfConfig::Get()->shelf_size());
  } else {
    EXPECT_EQ(gfx::Point(150, get_expected_y()), no_windows_centerpoint);
  }
}

// Tests that toggling overview on removes any resize shadows that may have been
// present.
TEST_P(OverviewSessionTest, DragMinimizedWindowHasStableSize) {
  UpdateDisplay(base::StringPrintf("1920x1200*%s", display::kDsfStr_1_777));
  EnterTabletMode();
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  WindowState::Get(window.get())->Minimize();
  ToggleOverview();
  auto* overview_item = GetOverviewItemForWindow(window.get());
  auto* widget = overview_item->item_widget();

  gfx::Rect workarea =
      display::Screen::GetScreen()->GetPrimaryDisplay().work_area();

  gfx::PointF drag_point(workarea.CenterPoint());
  GetOverviewSession()->InitiateDrag(overview_item, drag_point,
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/overview_item);
  gfx::Size target_size =
      GetTransformedTargetBounds(widget->GetNativeWindow()).size();

  drag_point.Offset(0, 10.5f);
  GetOverviewSession()->Drag(overview_item, drag_point);
  gfx::Size new_target_size =
      GetTransformedTargetBounds(widget->GetNativeWindow()).size();
  EXPECT_EQ(target_size, new_target_size);
  target_size = new_target_size;

  drag_point.Offset(0, 10.5f);
  GetOverviewSession()->Drag(overview_item, drag_point);
  EXPECT_EQ(target_size,
            GetTransformedTargetBounds(widget->GetNativeWindow()).size());

  GetOverviewSession()->CompleteDrag(overview_item, drag_point);
}

// Tests that the bounds of the grid do not intersect the shelf or its hotseat.
TEST_P(OverviewSessionTest, OverviewGridBounds) {
  EnterTabletMode();
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  ToggleOverview();
  ASSERT_TRUE(GetOverviewSession());

  Shelf* shelf = Shelf::ForWindow(Shell::GetPrimaryRootWindow());
  const gfx::Rect shelf_bounds = shelf->GetIdealBounds();
  EXPECT_FALSE(GetGridBounds().Intersects(shelf_bounds));

  if (!features::IsForestFeatureEnabled()) {
    const gfx::Rect hotseat_bounds =
        shelf->hotseat_widget()->GetWindowBoundsInScreen();
    EXPECT_FALSE(GetGridBounds().Intersects(hotseat_bounds));
  }
}

TEST_P(OverviewSessionTest, NoWindowsIndicatorPositionSplitview) {
  UpdateDisplay("400x300");
  EnterTabletMode();
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  ToggleOverview();
  ASSERT_TRUE(GetOverviewSession());
  RoundedLabelWidget* no_windows_widget =
      GetOverviewSession()->grid_list()[0]->no_windows_widget();
  EXPECT_FALSE(no_windows_widget);

  // Tests that when snapping a window to the left in splitview, the no windows
  // indicator shows up in the middle of the right side of the screen.
  GetSplitViewController()->SnapWindow(window.get(), SnapPosition::kPrimary);
  no_windows_widget = GetOverviewSession()->grid_list()[0]->no_windows_widget();
  ASSERT_TRUE(no_windows_widget);

  // Take that into account of the divider width.
  const int bounds_left = 200 + kSplitviewDividerShortSideLength / 2;
  int expected_x = bounds_left + (400 - (bounds_left)) / 2;
  const int expected_y = (300 - ShelfConfig::Get()->in_app_shelf_size()) / 2;

  // The x location should be in the center. The y location is roughly in the
  // center. A lot of calculations go towards the padding and birch and desks
  // bar for the y location.
  gfx::Point no_windows_centerpoint =
      no_windows_widget->GetWindowBoundsInScreen().CenterPoint();
  EXPECT_EQ(gfx::Point(expected_x, expected_y), no_windows_centerpoint);

  // Tests that when snapping a window to the right in splitview, the no windows
  // indicator shows up in the middle of the left side of the screen.
  GetSplitViewController()->SnapWindow(window.get(), SnapPosition::kSecondary);
  no_windows_centerpoint =
      no_windows_widget->GetWindowBoundsInScreen().CenterPoint();
  expected_x = /*bounds_right=*/(200 - 4) / 2;
  EXPECT_EQ(gfx::Point(expected_x, expected_y), no_windows_centerpoint);
}

// Tests that the no windows indicator shows properly after adding an item.
TEST_P(OverviewSessionTest, NoWindowsIndicatorAddItem) {
  EnterTabletMode();
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  ToggleOverview();
  GetSplitViewController()->SnapWindow(window.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(GetOverviewSession()->grid_list()[0]->no_windows_widget());

  GetOverviewSession()->AddItem(window.get(), /*reposition=*/true,
                                /*animate=*/false, /*ignored_items=*/{},
                                /*index=*/0u);
  EXPECT_FALSE(GetOverviewSession()->grid_list()[0]->no_windows_widget());
}

// Tests that we do not exit overview mode until all the grids are empty.
TEST_P(OverviewSessionTest, ExitOverviewWhenAllGridsEmpty) {
  UpdateDisplay("500x400,500x400,500x400");

  // Create two windows with widgets (widgets are needed to close the windows
  // later in the test), one each on the first two monitors.
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  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));
  aura::Window* window1 = widget1->GetNativeWindow();
  aura::Window* window2 = widget2->GetNativeWindow();
  ASSERT_TRUE(
      window_util::MoveWindowToDisplay(window2, GetSecondaryDisplay().id()));
  ASSERT_EQ(root_windows[0], window1->GetRootWindow());
  ASSERT_EQ(root_windows[1], window2->GetRootWindow());

  // Enter overview mode. Verify that the no windows indicator is not visible on
  // any display.
  ToggleOverview();
  auto& grids = GetOverviewSession()->grid_list();
  ASSERT_TRUE(GetOverviewSession());
  ASSERT_EQ(3u, grids.size());
  for (auto& grid : grids)
    EXPECT_FALSE(grid->no_windows_widget());

  OverviewItem* item1 =
      static_cast<OverviewItem*>(GetOverviewItemForWindow(window1));
  OverviewItem* item2 =
      static_cast<OverviewItem*>(GetOverviewItemForWindow(window2));
  ASSERT_TRUE(item1 && item2);

  // Close `item2`. Verify that we are still in overview mode because `window1`
  // is still open. All the grids should not have a no windows widget.
  item2->CloseWindow();
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(GetOverviewSession());
  ASSERT_EQ(3u, grids.size());
  EXPECT_FALSE(grids[0]->empty());
  EXPECT_TRUE(grids[1]->empty());
  EXPECT_TRUE(grids[2]->empty());
  for (auto& grid : grids)
    EXPECT_FALSE(grid->no_windows_widget());

  // Close `item1`. Verify that since no windows are open, we exit overview
  // mode.
  item1->CloseWindow();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(GetOverviewSession());
}

// Tests window list animation states are correctly updated.
TEST_P(OverviewSessionTest, SetWindowListAnimationStates) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());
  wm::ActivateWindow(window3.get());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen());

  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event);
  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen());

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  // Enter overview.
  ToggleOverview();
  EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating());
  EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
  EXPECT_FALSE(window3->layer()->GetAnimator()->is_animating());

  ToggleOverview();
}

// Tests window list animation states are correctly updated with selected
// window.
TEST_P(OverviewSessionTest, SetWindowListAnimationStatesWithSelectedWindow) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());
  wm::ActivateWindow(window3.get());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen());

  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event);
  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen());

  // Enter overview.
  ToggleOverview();

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  // Click on |window3| to activate it and exit overview.
  // Should only set |should_animate_when_exiting_| and
  // |should_be_observed_when_exiting_| on window 3.
  TweenTester tester1(window1.get());
  TweenTester tester2(window2.get());
  TweenTester tester3(window3.get());
  ClickWindow(window3.get());
  EXPECT_EQ(gfx::Tween::ZERO, tester1.tween_type());
  EXPECT_EQ(gfx::Tween::ZERO, tester2.tween_type());
  EXPECT_EQ(gfx::Tween::EASE_OUT, tester3.tween_type());
}

// Tests OverviewWindowAnimationObserver can handle deleted window.
TEST_P(OverviewSessionTest,
       OverviewWindowAnimationObserverCanHandleDeletedWindow) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());
  wm::ActivateWindow(window3.get());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen());

  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event);
  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen());

  // Enter overview.
  ToggleOverview();

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  // Click on |window3| to activate it and exit overview.
  // Should only set |should_animate_when_exiting_| and
  // |should_be_observed_when_exiting_| on window 3.
  {
    TweenTester tester1(window1.get());
    TweenTester tester2(window2.get());
    TweenTester tester3(window3.get());
    ClickWindow(window3.get());
    EXPECT_EQ(gfx::Tween::ZERO, tester1.tween_type());
    EXPECT_EQ(gfx::Tween::ZERO, tester2.tween_type());
    EXPECT_EQ(gfx::Tween::EASE_OUT, tester3.tween_type());
  }
  // Destroy |window1| and |window2| before |window3| finishes animation can be
  // handled in OverviewWindowAnimationObserver.
  window1.reset();
  window2.reset();
}

// Tests can handle OverviewWindowAnimationObserver was deleted.
TEST_P(OverviewSessionTest, HandleOverviewWindowAnimationObserverWasDeleted) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());
  wm::ActivateWindow(window3.get());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen());

  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event);
  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen());

  // Enter overview.
  ToggleOverview();

  // Click on |window2| to activate it and exit overview. Should only set
  // |should_animate_when_exiting_| and |should_be_observed_when_exiting_| on
  // window 2. Because the animation duration is zero in test, the
  // OverviewWindowAnimationObserver will delete itself immediately before
  // |window3| is added to it.
  ClickWindow(window2.get());
  EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
  EXPECT_FALSE(window2->layer()->GetAnimator()->is_animating());
  EXPECT_FALSE(window3->layer()->GetAnimator()->is_animating());
}

// Tests can handle |gained_active| window is not in the |overview_grid| when
// OnWindowActivated.
TEST_P(OverviewSessionTest, HandleActiveWindowNotInOverviewGrid) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());
  wm::ActivateWindow(window3.get());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen());

  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event);
  EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen());

  // Enter overview.
  ToggleOverview();

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  // Create and active a new window should exit overview without error.
  auto widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);

  TweenTester tester1(window1.get());
  TweenTester tester2(window2.get());
  TweenTester tester3(window3.get());

  ClickWindow(widget->GetNativeWindow());

  // `window1` and `window2` should animate.
  EXPECT_EQ(gfx::Tween::ACCEL_20_DECEL_100, tester1.tween_type());
  EXPECT_EQ(gfx::Tween::ACCEL_20_DECEL_100, tester2.tween_type());
  EXPECT_EQ(gfx::Tween::ZERO, tester3.tween_type());
}

// Tests that AlwaysOnTopWindow can be handled correctly in new overview
// animations.
TEST_P(OverviewSessionTest, HandleAlwaysOnTopWindow) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window5(
      CreateTestWindow(gfx::Rect(200, 200, 400, 400)));
  std::unique_ptr<aura::Window> window6(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window7(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window8(CreateTestWindow(bounds));
  window3->SetProperty(aura::client::kZOrderingKey,
                       ui::ZOrderLevel::kFloatingWindow);
  window5->SetProperty(aura::client::kZOrderingKey,
                       ui::ZOrderLevel::kFloatingWindow);

  // Control z order and MRU order.
  wm::ActivateWindow(window8.get());
  wm::ActivateWindow(window7.get());  // Will be fullscreen.
  wm::ActivateWindow(window6.get());  // Will be maximized.
  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window4.get());
  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window2.get());  // Will be fullscreen.
  wm::ActivateWindow(window1.get());

  const WMEvent toggle_maximize_event(WM_EVENT_TOGGLE_MAXIMIZE);
  WindowState::Get(window6.get())->OnWMEvent(&toggle_maximize_event);
  const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event);
  ASSERT_TRUE(WindowState::Get(window2.get())->IsFullscreen());
  ASSERT_TRUE(WindowState::Get(window7.get())->IsFullscreen());
  ASSERT_TRUE(WindowState::Get(window6.get())->IsMaximized());

  // Helper to check if `window` is visibly animating. In some overview
  // animations, we use tween zero, so there is no visible animation though it
  // technically is animating according to the ui::LayerAnimator API.
  auto is_visibly_animating = [](aura::Window* window) -> bool {
    ui::LayerAnimatorTestController controller(window->layer()->GetAnimator());
    ui::LayerAnimationSequence* sequence =
        controller.GetRunningSequence(ui::LayerAnimationElement::TRANSFORM);
    if (!sequence)
      return false;
    // There's only one element per sequence in the overview animation so this
    // is fine.
    ui::LayerAnimationElement* element = sequence->FirstElement();
    if (!element)
      return false;
    return element->tween_type() != gfx::Tween::ZERO;
  };

  // Case 1: Click on `window1` to activate it and exit overview.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  ToggleOverview();
  // For entering animation, only animate `window1`, `window2`, `window3` and
  // `window5`. `window2` is fullscreen so all windows except `window1`,
  // `window3` and `window5` are occluded.
  EXPECT_TRUE(is_visibly_animating(window1.get()));
  EXPECT_TRUE(is_visibly_animating(window2.get()));
  EXPECT_TRUE(is_visibly_animating(window3.get()));
  EXPECT_FALSE(is_visibly_animating(window4.get()));
  EXPECT_TRUE(is_visibly_animating(window5.get()));
  EXPECT_FALSE(is_visibly_animating(window6.get()));
  EXPECT_FALSE(is_visibly_animating(window7.get()));
  EXPECT_FALSE(is_visibly_animating(window8.get()));
  WaitForOverviewEnterAnimation();

  // Click on `window1` to activate it and exit overview. `window2` occludes
  // everything but `window1`, `window3` and `window5`, and `window1` is
  // occluded by `window3`. So `window2`, `window3` and `window5` should be
  // animated.
  ClickWindow(window1.get());
  EXPECT_FALSE(is_visibly_animating(window1.get()));
  EXPECT_TRUE(is_visibly_animating(window2.get()));
  EXPECT_TRUE(is_visibly_animating(window3.get()));
  EXPECT_FALSE(is_visibly_animating(window4.get()));
  EXPECT_TRUE(is_visibly_animating(window5.get()));
  EXPECT_FALSE(is_visibly_animating(window6.get()));
  EXPECT_FALSE(is_visibly_animating(window7.get()));
  EXPECT_FALSE(is_visibly_animating(window8.get()));
  WaitForOverviewExitAnimation();

  // Case 2: Click on `window3` to activate it and exit overview. Since
  // `window2` is fullscreen, all windows after it are occluded, except
  // `window3` and `window5`, which are always on top. `window1` is not occluded
  // by `window2` but has the same bounds as `window3` so is occluded.
  // Reset window z-order. Need to toggle fullscreen first to workaround
  // https://crbug.com/816224.
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event);
  wm::ActivateWindow(window8.get());
  wm::ActivateWindow(window7.get());  // Will be fullscreen.
  wm::ActivateWindow(window6.get());  // Maximized.
  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window4.get());
  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window2.get());  // Will be fullscreen.
  wm::ActivateWindow(window1.get());
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event);

  // Enter overview.
  ToggleOverview();
  WaitForOverviewEnterAnimation();

  ClickWindow(window3.get());
  EXPECT_FALSE(is_visibly_animating(window1.get()));
  EXPECT_TRUE(is_visibly_animating(window2.get()));
  EXPECT_TRUE(is_visibly_animating(window3.get()));
  EXPECT_FALSE(is_visibly_animating(window4.get()));
  EXPECT_TRUE(is_visibly_animating(window5.get()));
  EXPECT_FALSE(is_visibly_animating(window6.get()));
  EXPECT_FALSE(is_visibly_animating(window7.get()));
  EXPECT_FALSE(is_visibly_animating(window8.get()));
  WaitForOverviewExitAnimation();

  // Case 3: Click on maximized `window6` to activate it and exit overview.
  // `window6` will become the topmost regular z-order window and will occlude
  // everything except `window2` as it is fullscreen and `window3` and `window5`
  // as they are always on top. Reset window z-order. Need to toggle fullscreen
  // first to workaround https://crbug.com/816224.
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event);
  wm::ActivateWindow(window8.get());
  wm::ActivateWindow(window7.get());  // Will be fullscreen.
  wm::ActivateWindow(window6.get());  // Maximized.
  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window4.get());
  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window2.get());  // Will be fullscreen.
  wm::ActivateWindow(window1.get());
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event);
  // Enter overview.
  ToggleOverview();
  WaitForOverviewEnterAnimation();

  ClickWindow(window6.get());
  EXPECT_FALSE(is_visibly_animating(window1.get()));
  EXPECT_FALSE(is_visibly_animating(window2.get()));
  EXPECT_TRUE(is_visibly_animating(window3.get()));
  EXPECT_FALSE(is_visibly_animating(window4.get()));
  EXPECT_TRUE(is_visibly_animating(window5.get()));
  EXPECT_TRUE(is_visibly_animating(window6.get()));
  EXPECT_FALSE(is_visibly_animating(window7.get()));
  EXPECT_FALSE(is_visibly_animating(window8.get()));
  WaitForOverviewExitAnimation();

  // Case 4: Click on `window8` to activate it and exit overview.
  // Should animate `window8`, `window1`, `window2`, `window3` and `window5`
  // because `window3` and `window5` are AlwaysOnTop windows and `window2` is
  // fullscreen.
  // Reset window z-order. Need to toggle fullscreen first to workaround
  // https://crbug.com/816224.
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event);
  wm::ActivateWindow(window8.get());
  wm::ActivateWindow(window7.get());  // Will be fullscreen.
  wm::ActivateWindow(window6.get());  // Maximized.
  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window4.get());
  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
  wm::ActivateWindow(window2.get());  // Will be fullscreen.
  wm::ActivateWindow(window1.get());
  WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event);
  WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event);

  // Enter overview.
  ToggleOverview();
  WaitForOverviewEnterAnimation();

  ClickWindow(window8.get());
  EXPECT_FALSE(is_visibly_animating(window1.get()));
  EXPECT_TRUE(is_visibly_animating(window2.get()));
  EXPECT_TRUE(is_visibly_animating(window3.get()));
  EXPECT_FALSE(is_visibly_animating(window4.get()));
  EXPECT_TRUE(is_visibly_animating(window5.get()));
  EXPECT_FALSE(is_visibly_animating(window6.get()));
  EXPECT_FALSE(is_visibly_animating(window7.get()));
  EXPECT_TRUE(is_visibly_animating(window8.get()));
  WaitForOverviewExitAnimation();
}

// Verify that the selector item can animate after the item is dragged and
// released.
TEST_P(OverviewSessionTest, WindowItemCanAnimateOnDragRelease) {
  base::HistogramTester histogram_tester;
  UpdateDisplay("500x400");
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EnterTabletMode();
  ToggleOverview();
  auto* item2 = GetOverviewItemForWindow(window2.get());
  // Drag |item2| in a way so that |window2| does not get activated.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(
      gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  base::RunLoop().RunUntilIdle();

  generator->MoveMouseTo(gfx::Point(250, 200));
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1);
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 0);

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  generator->ReleaseLeftButton();
  EXPECT_TRUE(window2->layer()->GetAnimator()->IsAnimatingProperty(
      ui::LayerAnimationElement::AnimatableProperty::TRANSFORM));
  base::RunLoop().RunUntilIdle();
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1);
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 1);
}

// Verify that the overview items titlebar and close button change visibility
// when a item is being dragged.
TEST_P(OverviewSessionTest, OverviewItemTitleCloseVisibilityOnDrag) {
  base::HistogramTester histogram_tester;
  UpdateDisplay("500x400");
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  EnterTabletMode();
  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  // Start the drag on |item1|. Verify the dragged item, |item1| has both the
  // close button and titlebar hidden. The close button opacity however is
  // opaque as its a child of the header which handles fading away the whole
  // header. All other items, |item2| should only have the close button hidden.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(
      gfx::ToRoundedPoint(item1->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  base::RunLoop().RunUntilIdle();

  // The title is always shown and the layer is created only after the drag has
  // started moving.
  EXPECT_TRUE(GetCloseButton(item1)->layer());
  EXPECT_EQ(0.f, GetCloseButtonOpacity(item1));
  EXPECT_TRUE(GetCloseButton(item2)->layer());
  EXPECT_EQ(0.f, GetCloseButtonOpacity(item2));

  // Drag |item1| in a way so that |window1| does not get activated (drags
  // within a certain threshold count as clicks). Verify the close button and
  // titlebar is visible for all items.
  generator->MoveMouseTo(gfx::Point(250, 200));
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1);
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 0);

  generator->ReleaseLeftButton();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1.f, GetTitlebarOpacity(item1));
  EXPECT_EQ(1.f, GetCloseButtonOpacity(item1));
  EXPECT_EQ(1.f, GetTitlebarOpacity(item2));
  EXPECT_EQ(1.f, GetCloseButtonOpacity(item2));
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1);
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 1);
}

// Tests that overview widgets are stacked in the correct order.
TEST_P(OverviewSessionTest, OverviewWidgetStackingOrder) {
  base::HistogramTester histogram_tester;
  // Create three windows, including one minimized.
  std::unique_ptr<aura::Window> minimized(CreateTestWindow());
  WindowState::Get(minimized.get())->Minimize();
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());

  aura::Window* parent = window->parent();
  EXPECT_EQ(parent, minimized->parent());

  EnterTabletMode();
  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(minimized.get());
  auto* item2 = GetOverviewItemForWindow(window.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());

  views::Widget* widget1 = item1->item_widget();
  views::Widget* widget2 = item2->item_widget();
  views::Widget* widget3 = item3->item_widget();

  // The original order of stacking is determined by the order the associated
  // window was activated.
  EXPECT_TRUE(window_util::IsStackedBelow(widget2->GetNativeWindow(),
                                          widget3->GetNativeWindow()));
  EXPECT_TRUE(window_util::IsStackedBelow(widget1->GetNativeWindow(),
                                          widget2->GetNativeWindow()));

  // Verify that the item widget is stacked below the window.
  EXPECT_TRUE(
      window_util::IsStackedBelow(widget1->GetNativeWindow(), minimized.get()));
  EXPECT_TRUE(
      window_util::IsStackedBelow(widget2->GetNativeWindow(), window.get()));
  EXPECT_TRUE(
      window_util::IsStackedBelow(widget3->GetNativeWindow(), window3.get()));

  // Drag the first window. Verify that it's item widget is not stacked above
  // the other two.
  const gfx::Point start_drag =
      gfx::ToRoundedPoint(item1->target_bounds().CenterPoint());
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(start_drag);
  generator->PressLeftButton();
  EXPECT_TRUE(window_util::IsStackedBelow(widget2->GetNativeWindow(),
                                          widget1->GetNativeWindow()));
  EXPECT_TRUE(window_util::IsStackedBelow(widget3->GetNativeWindow(),
                                          widget1->GetNativeWindow()));

  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 0);

  // Drag to origin and then back to the start to avoid activating the window or
  // entering splitview.
  generator->MoveMouseTo(gfx::Point());
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1);

  generator->MoveMouseTo(start_drag);
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 2);
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 0);

  generator->ReleaseLeftButton();

  // Verify the stacking order is same as before dragging started.
  EXPECT_TRUE(window_util::IsStackedBelow(widget2->GetNativeWindow(),
                                          widget3->GetNativeWindow()));
  EXPECT_TRUE(window_util::IsStackedBelow(widget1->GetNativeWindow(),
                                          widget2->GetNativeWindow()));

  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 2);
  histogram_tester.ExpectTotalCount(
      "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 1);
}

// Test that dragging an overview item to snap creates a drop target stacked at
// the bottom. Test that ending the drag removes the drop target.
TEST_P(OverviewSessionTest, DropTargetStackedAtBottomForOverviewItem) {
  EnterTabletMode();
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  aura::Window* parent = window1->parent();
  ASSERT_EQ(parent, window2->parent());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());
  ToggleOverview();
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(gfx::ToRoundedPoint(
      GetOverviewItemForWindow(window1.get())->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  generator->MoveMouseBy(5, 0);
  ASSERT_TRUE(GetDropTarget(0));
  EXPECT_TRUE(window_util::IsStackedBelow(
      GetDropTarget(0)->item_widget()->GetNativeWindow(), window2.get()));
  generator->ReleaseLeftButton();
  EXPECT_FALSE(GetDropTarget(0));
}

// Verify that a windows which enter overview mode have a visible backdrop, if
// the window is to be letter or pillar fitted.
TEST_P(OverviewSessionTest, Backdrop) {
  // Add three windows which in overview mode will be considered wide, tall and
  // normal. Window |wide|, with size (400, 160) will be resized to (300, 160)
  // when the 400x300 is rotated to 300x400, and should be considered a normal
  // overview window after display change.
  UpdateDisplay("400x300");
  std::unique_ptr<aura::Window> wide(CreateTestWindow(gfx::Rect(400, 160)));
  std::unique_ptr<aura::Window> tall(CreateTestWindow(gfx::Rect(100, 300)));
  std::unique_ptr<aura::Window> normal(CreateTestWindow(gfx::Rect(300, 300)));

  ToggleOverview();
  base::RunLoop().RunUntilIdle();
  auto* wide_item = GetOverviewItemForWindow(wide.get());
  auto* tall_item = GetOverviewItemForWindow(tall.get());
  auto* normal_item = GetOverviewItemForWindow(normal.get());

  // Only very tall and very wide windows will have a backdrop. The backdrop
  // only gets created if we need it once during the overview session.
  ASSERT_TRUE(GetBackdropView(wide_item));
  EXPECT_TRUE(GetBackdropView(wide_item)->GetVisible());
  EXPECT_TRUE(GetBackdropView(tall_item));
  ASSERT_TRUE(GetBackdropView(tall_item)->GetVisible());
  EXPECT_FALSE(GetBackdropView(normal_item));

  display::Screen* screen = display::Screen::GetScreen();
  const display::Display& display = screen->GetPrimaryDisplay();
  display_manager()->SetDisplayRotation(
      display.id(), display::Display::ROTATE_90,
      display::Display::RotationSource::ACTIVE);

  // After rotation the former wide window will be a normal window and its
  // backdrop will still be there but invisible.
  ASSERT_TRUE(GetBackdropView(wide_item));
  EXPECT_FALSE(GetBackdropView(wide_item)->GetVisible());
  EXPECT_TRUE(GetBackdropView(tall_item));
  ASSERT_TRUE(GetBackdropView(tall_item)->GetVisible());
  EXPECT_FALSE(GetBackdropView(normal_item));

  // Test that leaving overview mode cleans up properly.
  ToggleOverview();
}

// Test that the rounded corners are removed during animations.
TEST_P(OverviewSessionTest, RoundedCornersVisibility) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Test that entering overview mode normally will disable all the rounded
  // corners until the animation is complete.
  EnterTabletMode();
  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  EXPECT_FALSE(HasRoundedCorner(item1));
  EXPECT_FALSE(HasRoundedCorner(item2));
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kEnterAnimationComplete);
  EXPECT_TRUE(HasRoundedCorner(item1));
  EXPECT_TRUE(HasRoundedCorner(item2));

  // Tests that entering overview mode with all windows minimized (launcher
  // button pressed) will still disable all the rounded corners until the
  // animation is complete.
  ToggleOverview();
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kExitAnimationComplete);
  WindowState::Get(window1.get())->Minimize();
  WindowState::Get(window2.get())->Minimize();

  ToggleOverview();
  item1 = GetOverviewItemForWindow(window1.get());
  item2 = GetOverviewItemForWindow(window2.get());
  EXPECT_FALSE(HasRoundedCorner(item1));
  EXPECT_FALSE(HasRoundedCorner(item2));
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kEnterAnimationComplete);
  EXPECT_TRUE(HasRoundedCorner(item1));
  EXPECT_TRUE(HasRoundedCorner(item2));

  // Test that leaving overview mode cleans up properly.
  ToggleOverview();
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kExitAnimationComplete);
}

// Test that the shadow disappears while dragging an overview item.
TEST_P(OverviewSessionTest, ShadowVisibilityDragging) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());

  EnterTabletMode();
  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Drag the first window. Verify that the shadow was removed for the first
  // window but still exists for the second window as we do not make shadow
  // for a dragged window.
  const gfx::Point start_drag =
      gfx::ToRoundedPoint(item1->target_bounds().CenterPoint());
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(start_drag);
  generator->PressLeftButton();
  EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
  EXPECT_FALSE(window2->layer()->GetAnimator()->is_animating());

  EXPECT_TRUE(GetShadowBounds(item1).IsEmpty());
  EXPECT_FALSE(GetShadowBounds(item2).IsEmpty());

  // Drag to horizontally and then back to the start to avoid activating the
  // window, drag to close or entering splitview. Verify that the shadow is
  // invisible on both items during animation.
  generator->MoveMouseTo(gfx::Point(0, start_drag.y()));

  // The drop target window should be created with no shadow.
  auto* drop_target_item = GetDropTarget(0);
  ASSERT_TRUE(drop_target_item);
  EXPECT_TRUE(GetShadowBounds(drop_target_item).IsEmpty());

  window1->layer()->GetAnimator()->StopAnimating();
  window2->layer()->GetAnimator()->StopAnimating();

  generator->MoveMouseTo(start_drag);
  generator->ReleaseLeftButton();
  EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating());
  EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
  EXPECT_TRUE(GetShadowBounds(item1).IsEmpty());
  EXPECT_TRUE(GetShadowBounds(item2).IsEmpty());

  // Verify that the shadow is visble again after animation is finished.
  window1->layer()->GetAnimator()->StopAnimating();
  window2->layer()->GetAnimator()->StopAnimating();
  EXPECT_FALSE(GetShadowBounds(item1).IsEmpty());
  EXPECT_FALSE(GetShadowBounds(item2).IsEmpty());
}

// Tests that the shadows in overview mode are placed correctly.
TEST_P(OverviewSessionTest, ShadowBounds) {
  // Helper function to check if the bounds of a shadow owned by |shadow_parent|
  // is contained within the bounds of |widget|.
  auto contains = [&](views::Widget* widget, OverviewItemBase* shadow_parent) {
    return gfx::Rect(widget->GetNativeWindow()->bounds().size())
        .Contains(GetShadowBounds(shadow_parent));
  };

  // Helper function which returns the ratio of the shadow owned by
  // |shadow_parent| width and height.
  auto shadow_ratio = [&](OverviewItemBase* shadow_parent) {
    gfx::RectF boundsf = gfx::RectF(GetShadowBounds(shadow_parent));
    return boundsf.width() / boundsf.height();
  };

  // Helper function which returns the ratio of the item width and height minus
  // the header and window margin.
  auto item_ratio = [](OverviewItemBase* item) {
    const gfx::RectF boundsf = item->target_bounds();
    return boundsf.width() / boundsf.height();
  };

  // Add three windows which in overview mode will be considered wide, tall and
  // normal. Set top view insets to 0 so it is easy to check the ratios of the
  // shadows match the ratios of the untransformed windows.
  UpdateDisplay("900x800");
  std::unique_ptr<aura::Window> wide(
      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(400, 100)));
  std::unique_ptr<aura::Window> tall(
      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(100, 400)));
  std::unique_ptr<aura::Window> normal(
      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(200, 200)));
  wide->SetProperty(aura::client::kTopViewInset, 0);
  tall->SetProperty(aura::client::kTopViewInset, 0);
  normal->SetProperty(aura::client::kTopViewInset, 0);

  ToggleOverview();
  base::RunLoop().RunUntilIdle();
  auto* wide_item = GetOverviewItemForWindow(wide.get());
  auto* tall_item = GetOverviewItemForWindow(tall.get());
  auto* normal_item = GetOverviewItemForWindow(normal.get());

  views::Widget* wide_widget = wide_item->item_widget();
  views::Widget* tall_widget = tall_item->item_widget();
  views::Widget* normal_widget = normal_item->item_widget();

  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();

  // Verify all the shadows are within the bounds of their respective item
  // widgets when the overview windows are positioned without animations.
  SetGridBounds(grid, gfx::Rect(400, 800));
  grid->PositionWindows(false);
  EXPECT_TRUE(contains(wide_widget, wide_item));
  EXPECT_TRUE(contains(tall_widget, tall_item));
  EXPECT_TRUE(contains(normal_widget, normal_item));

  // Verify the shadow of window with normal type preserves the ratio of the
  // original window. Otherwise, it preserves the ratio of the item bounds minus
  // the header of window margin.
  EXPECT_NEAR(shadow_ratio(wide_item), item_ratio(wide_item), 0.01f);
  EXPECT_NEAR(shadow_ratio(tall_item), item_ratio(tall_item), 0.01f);
  EXPECT_NEAR(shadow_ratio(normal_item), item_ratio(normal_item), 0.01f);

  // Verify all the shadows are within the bounds of their respective item
  // widgets when the overview windows are positioned with animations.
  SetGridBounds(grid, gfx::Rect(400, 800));
  grid->PositionWindows(true);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(contains(wide_widget, wide_item));
  EXPECT_TRUE(contains(tall_widget, tall_item));
  EXPECT_TRUE(contains(normal_widget, normal_item));

  EXPECT_NEAR(shadow_ratio(wide_item), item_ratio(wide_item), 0.01f);
  EXPECT_NEAR(shadow_ratio(tall_item), item_ratio(tall_item), 0.01f);
  EXPECT_NEAR(shadow_ratio(normal_item), item_ratio(normal_item), 0.01f);

  // Test that leaving overview mode cleans up properly.
  ToggleOverview();
}

// Verify that attempting to drag with a secondary finger works as expected.
TEST_P(OverviewSessionTest, DraggingWithTwoFingers) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  EnterTabletMode();
  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());

  const gfx::RectF original_bounds1 = item1->target_bounds();
  const gfx::RectF original_bounds2 = item2->target_bounds();

  constexpr int kTouchId1 = 1;
  constexpr int kTouchId2 = 2;

  // Dispatches a long press event at the event generators current location.
  // Long press is one way to start dragging in splitview.
  auto dispatch_long_press = [this]() {
    ui::GestureEventDetails event_details(ui::EventType::kGestureLongPress);
    const gfx::Point location = GetEventGenerator()->current_screen_location();
    ui::GestureEvent long_press(location.x(), location.y(), 0,
                                ui::EventTimeForNow(), event_details);
    GetEventGenerator()->Dispatch(&long_press);
  };

  // Verify that the bounds of the tapped window expand when touched.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(original_bounds1.CenterPoint()));
  generator->PressTouchId(kTouchId1);
  dispatch_long_press();
  EXPECT_GT(item1->target_bounds().width(), original_bounds1.width());
  EXPECT_GT(item1->target_bounds().height(), original_bounds1.height());

  // Verify that attempting to touch the second window with a second finger does
  // nothing to the second window. The first window remains the window to be
  // dragged.
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(original_bounds2.CenterPoint()));
  generator->PressTouchId(kTouchId2);
  dispatch_long_press();
  EXPECT_GT(item1->target_bounds().width(), original_bounds1.width());
  EXPECT_GT(item1->target_bounds().height(), original_bounds1.height());
  EXPECT_EQ(item2->target_bounds(), original_bounds2);

  // Verify the first window moves on drag.
  gfx::PointF last_center_point = item1->target_bounds().CenterPoint();
  generator->MoveTouchIdBy(kTouchId1, 40, 40);
  EXPECT_NE(last_center_point, item1->target_bounds().CenterPoint());
  EXPECT_EQ(original_bounds2.CenterPoint(),
            item2->target_bounds().CenterPoint());

  // Verify the first window moves on drag, even if we switch to a second
  // finger.
  last_center_point = item1->target_bounds().CenterPoint();
  generator->ReleaseTouchId(kTouchId2);
  generator->PressTouchId(kTouchId2);
  generator->MoveTouchIdBy(kTouchId2, 40, 40);
  EXPECT_NE(last_center_point, item1->target_bounds().CenterPoint());
  EXPECT_EQ(original_bounds2.CenterPoint(),
            item2->target_bounds().CenterPoint());
}

// Verify that shadows on windows disappear for the duration of overview mode.
TEST_P(OverviewSessionTest, ShadowDisappearsInOverview) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());

  // Verify that the shadow is initially visible.
  ::wm::ShadowController* shadow_controller = Shell::Get()->shadow_controller();
  EXPECT_TRUE(shadow_controller->IsShadowVisibleForWindow(window.get()));

  // Verify that the shadow is invisible after entering overview mode.
  ToggleOverview();
  EXPECT_FALSE(shadow_controller->IsShadowVisibleForWindow(window.get()));

  // Verify that the shadow is visible again after exiting overview mode.
  ToggleOverview();
  EXPECT_TRUE(shadow_controller->IsShadowVisibleForWindow(window.get()));
}

// Verify that PIP windows will be excluded from the overview, but not hidden.
TEST_P(OverviewSessionTest, PipWindowShownButExcludedFromOverview) {
  std::unique_ptr<aura::Window> pip_window(
      CreateTestWindow(gfx::Rect(200, 200)));
  WindowState* window_state = WindowState::Get(pip_window.get());
  const WMEvent enter_pip(WM_EVENT_PIP);
  window_state->OnWMEvent(&enter_pip);

  // Enter overview.
  ToggleOverview();

  // PIP window should be visible but not in the overview.
  EXPECT_TRUE(pip_window->IsVisible());
  EXPECT_FALSE(GetOverviewItemForWindow(pip_window.get()));
}

// Tests the PositionWindows function works as expected.
TEST_P(OverviewSessionTest, PositionWindows) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());

  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());
  const gfx::RectF bounds1 = item1->target_bounds();
  const gfx::RectF bounds2 = item2->target_bounds();
  const gfx::RectF bounds3 = item3->target_bounds();

  // Verify that the bounds remain the same when calling PositionWindows again.
  GetOverviewSession()->PositionWindows(/*animate=*/false);
  EXPECT_EQ(bounds1, item1->target_bounds());
  EXPECT_EQ(bounds2, item2->target_bounds());
  EXPECT_EQ(bounds3, item3->target_bounds());

  // Verify that |item2| and |item3| change bounds when calling PositionWindows
  // while ignoring |item1|.
  GetOverviewSession()->PositionWindows(/*animate=*/false, {item1});
  EXPECT_EQ(bounds1, item1->target_bounds());
  EXPECT_NE(bounds2, item2->target_bounds());
  EXPECT_NE(bounds3, item3->target_bounds());

  // Return the windows to their original bounds.
  GetOverviewSession()->PositionWindows(/*animate=*/false);

  // Verify that items that are animating before closing are ignored by
  // PositionWindows.
  SetAnimatingToClose(item1, true);
  SetAnimatingToClose(item2, true);
  GetOverviewSession()->PositionWindows(/*animate=*/false);
  EXPECT_EQ(bounds1, item1->target_bounds());
  EXPECT_EQ(bounds2, item2->target_bounds());
  EXPECT_NE(bounds3, item3->target_bounds());
}

// Tests the grid bounds are as expected with different shelf auto hide
// behaviors and alignments.
TEST_P(OverviewSessionTest, GridBounds) {
  UpdateDisplay("700x600");
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(200, 200)));

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAlignment(ShelfAlignment::kBottom);
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);

  // Test that with the bottom shelf, the grid should take up the entire display
  // minus the shelf area on the bottom regardless of auto hide behavior.
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  ToggleOverview();
  EXPECT_EQ(gfx::Rect(0, 0, 700, 600 - shelf_size), GetGridBounds());
  ToggleOverview();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ToggleOverview();
  EXPECT_EQ(gfx::Rect(0, 0, 700, 600 - shelf_size), GetGridBounds());
  ToggleOverview();

  // Test that with the right shelf, the grid should take up the entire display
  // minus the shelf area on the right regardless of auto hide behavior.
  shelf->SetAlignment(ShelfAlignment::kRight);
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  ToggleOverview();
  EXPECT_EQ(gfx::Rect(0, 0, 700 - shelf_size, 600), GetGridBounds());
  ToggleOverview();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ToggleOverview();
  EXPECT_EQ(gfx::Rect(0, 0, 700 - shelf_size, 600), GetGridBounds());
  ToggleOverview();
}

// Tests that windows that have a backdrop can still be tapped normally.
// Regression test for crbug.com/938645.
TEST_P(OverviewSessionTest, SelectingWindowWithBackdrop) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(500, 200)));

  ToggleOverview();
  auto* item = GetOverviewItemForWindow(window.get());
  ASSERT_EQ(OverviewItemFillMode::kLetterBoxed,
            item->GetOverviewItemFillMode());

  // Tap the target.
  GetEventGenerator()->set_current_screen_location(
      gfx::ToRoundedPoint(item->target_bounds().CenterPoint()));
  GetEventGenerator()->ClickLeftButton();
  EXPECT_FALSE(InOverviewSession());
}

TEST_P(OverviewSessionTest, ShelfAlignmentChangeWhileInOverview) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAlignment(ShelfAlignment::kBottom);
  ToggleOverview();
  shelf->SetAlignment(ShelfAlignment::kRight);
  EXPECT_FALSE(InOverviewSession());
}

namespace {
class TestEventHandler : public ui::EventHandler {
 public:
  TestEventHandler() = default;
  ~TestEventHandler() override = default;
  // ui::EventHandler:
  void OnKeyEvent(ui::KeyEvent* event) override {
    if (event->type() != ui::EventType::kKeyPressed) {
      return;
    }

    has_seen_event_ = true;
    event->SetHandled();
    event->StopPropagation();
  }
  bool HasSeenEvent() { return has_seen_event_; }
  void Reset() { has_seen_event_ = false; }

 private:
  bool has_seen_event_ = false;
};
}  // namespace

// Test that keys are eaten when entering overview mode.
TEST_P(OverviewSessionTest, EatKeysDuringStartAnimation) {
  std::unique_ptr<aura::Window> test_window(CreateTestWindow());
  TestEventHandler test_event_handler;
  test_window->SetTargetHandler(&test_event_handler);
  test_window->Focus();

  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Keys shouldn't be eaten by overview session normally.
  PressAndReleaseKey(ui::VKEY_A);
  ASSERT_TRUE(test_window->HasFocus());
  EXPECT_TRUE(test_event_handler.HasSeenEvent());
  test_event_handler.Reset();

  // Keys should be eaten by overview session when entering overview mode.
  ToggleOverview();
  ASSERT_TRUE(OverviewController::Get()->IsInStartAnimation());
  ASSERT_TRUE(test_window->HasFocus());
  PressAndReleaseKey(ui::VKEY_B);
  EXPECT_FALSE(test_event_handler.HasSeenEvent());
  EXPECT_TRUE(InOverviewSession());

  WaitForOverviewEnterAnimation();
  ASSERT_FALSE(OverviewController::Get()->IsInStartAnimation());
  EXPECT_FALSE(test_window->HasFocus());

  ToggleOverview();
  PressAndReleaseKey(ui::VKEY_C);
  EXPECT_FALSE(InOverviewSession());
  EXPECT_TRUE(test_event_handler.HasSeenEvent());
}

// Tests that in tablet mode, tapping on the background will go to home screen.
TEST_P(OverviewSessionTest, TapOnBackgroundGoToHome) {
  EnterTabletMode();
  UpdateDisplay("800x600");
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  WindowState* window_state = WindowState::Get(window.get());

  EXPECT_FALSE(window_state->IsMinimized());
  EXPECT_FALSE(Shell::Get()->app_list_controller()->IsHomeScreenVisible());
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  // Tap on the background. The tap location should be out of the tapping area
  // for back gesture. Otherwise, the touch event will be consumed and no
  // gesture event will be generated.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  GetEventGenerator()->GestureTapAt(
      gfx::Point(BackGestureEventHandler::kStartGoingBackLeftEdgeInset, 10));
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kExitAnimationComplete);

  EXPECT_FALSE(InOverviewSession());
  EXPECT_TRUE(window_state->IsMinimized());
  EXPECT_TRUE(Shell::Get()->app_list_controller()->IsHomeScreenVisible());
}

// Tests that in tablet mode, tapping on the background in split view mode will
// be no-op.
TEST_P(OverviewSessionTest, TapOnBackgroundInSplitView) {
  EnterTabletMode();
  UpdateDisplay("800x600");
  std::unique_ptr<aura::Window> window1(CreateTestWindow());

  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  EXPECT_FALSE(Shell::Get()->app_list_controller()->IsHomeScreenVisible());
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  GetSplitViewController()->SnapWindow(window2.get(), SnapPosition::kSecondary);
  EXPECT_TRUE(GetSplitViewController()->InSplitViewMode());

  // Tap on the background.
  GetEventGenerator()->GestureTapAt(gfx::Point(10, 10));

  EXPECT_TRUE(InOverviewSession());
  EXPECT_FALSE(Shell::Get()->app_list_controller()->IsHomeScreenVisible());
  EXPECT_TRUE(GetSplitViewController()->InSplitViewMode());
}

// Tests starting the overview session using kFadeInEnter type.
TEST_P(OverviewSessionTest, FadeIn) {
  EnterTabletMode();
  // Create a minimized window.
  std::unique_ptr<aura::Window> window = CreateTestWindow();
  WindowState::Get(window.get())->Minimize();

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  ToggleOverview(OverviewEnterExitType::kFadeInEnter);
  ASSERT_TRUE(InOverviewSession());

  auto* item = GetOverviewItemForWindow(window.get())
                   ->GetLeafItemForWindow(window.get());

  // Verify that the item widget's transform is not animated as part of the
  // animation.
  views::Widget* widget = item->item_widget();
  EXPECT_FALSE(widget->GetLayer()->GetAnimator()->IsAnimatingProperty(
      ui::LayerAnimationElement::TRANSFORM));

  // Opacity should be animated to full opacity.
  EXPECT_EQ(1.0f, widget->GetLayer()->GetTargetOpacity());
  EXPECT_TRUE(widget->GetLayer()->GetAnimator()->IsAnimatingProperty(
      ui::LayerAnimationElement::OPACITY));

  // Validate item bounds are within the grid.
  const gfx::Rect bounds = gfx::ToEnclosedRect(item->target_bounds());
  EXPECT_TRUE(GetGridBounds().Contains(bounds));
  EXPECT_EQ(OverviewEnterExitType::kFadeInEnter,
            GetOverviewSession()->enter_exit_overview_type());
}

// Tests exiting the overview session using kFadeOutExit type.
TEST_P(OverviewSessionTest, FadeOutExit) {
  EnterTabletMode();
  std::unique_ptr<aura::Window> test_window(CreateAppWindow());

  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());
  EXPECT_FALSE(WindowState::Get(test_window.get())->IsMinimized());

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Grab the item widget before the session starts shutting down. The widget
  // should outlive the session, at least until the animations are done - given
  // that NON_ZERO_DURATION animation duration scale, it should be safe to
  // dereference the widget pointer immediately (synchronously) after the
  // session ends.
  auto* item = GetOverviewItemForWindow(test_window.get());
  views::Widget* grid_item_widget = item->item_widget();
  gfx::Rect item_bounds = grid_item_widget->GetWindowBoundsInScreen();

  ToggleOverview(OverviewEnterExitType::kFadeOutExit);
  ASSERT_FALSE(InOverviewSession());

  // The test window should be minimized as overview fade out exit starts.
  EXPECT_TRUE(WindowState::Get(test_window.get())->IsMinimized());

  // Verify that the item widget's transform is not animated as part of the
  // animation, and that item widget bounds are not changed after minimizing the
  // window.
  EXPECT_FALSE(grid_item_widget->GetLayer()->GetAnimator()->IsAnimatingProperty(
      ui::LayerAnimationElement::TRANSFORM));
  EXPECT_EQ(item_bounds, grid_item_widget->GetWindowBoundsInScreen());

  // Opacity should be animated to zero opacity.
  EXPECT_EQ(0.0f, grid_item_widget->GetLayer()->GetTargetOpacity());
  EXPECT_TRUE(grid_item_widget->GetLayer()->GetAnimator()->IsAnimatingProperty(
      ui::LayerAnimationElement::OPACITY));
}

// Tests that accessibility overrides are set as expected on overview related
// widgets.
TEST_P(OverviewSessionTest, AccessibilityFocusAnnotator) {
  // TODO(crbug.com/1360638): The body of this test is only run when Desk
  // Templates is turned OFF *and* Save & Recall is turned ON. Once the flag
  // flip for Save & Recall has truly landed, remove the `NoSavedDesks` variant
  // of this test below and remove the Save & Recall feature check at the start
  // of this test.
  if (DeskTemplatesOn() || !saved_desk_util::ShouldShowSavedDesksOptions()) {
    return;
  }

  base::AutoReset<bool> disable =
      OverviewController::Get()->SetDisableAppIdCheckForTests();

  auto window3 = CreateAppWindow(gfx::Rect(100, 100));
  auto window2 = CreateAppWindow(gfx::Rect(100, 100));
  auto window1 = CreateAppWindow(gfx::Rect(100, 100));

  ToggleOverview();
  WaitForOverviewEnterAnimation();

  auto* focus_widget = views::Widget::GetWidgetForNativeWindow(
      GetOverviewSession()->GetOverviewFocusWindow());
  ASSERT_TRUE(focus_widget);

  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
  auto* desk_widget = const_cast<views::Widget*>(grid->desks_widget());
  ASSERT_TRUE(desk_widget);

  // Overview items are in MRU order, so the expected order in the grid list is
  // the reverse creation order.
  auto* item_widget1 = GetOverviewItemForWindow(window1.get())->item_widget();
  auto* item_widget2 = GetOverviewItemForWindow(window2.get())->item_widget();
  auto* item_widget3 = GetOverviewItemForWindow(window3.get())->item_widget();

  // With this flag enabled, there are is no saved desk save desk container.
  if (features::IsSavedDeskUiRevampEnabled()) {
    // Order should be [focus_widget, item_widget1, item_widget2, item_widget3,
    // desk_widget, save_widget].
    CheckA11yOverrides("focus", focus_widget, desk_widget, item_widget1);
    CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget2);
    CheckA11yOverrides("item2", item_widget2, item_widget1, item_widget3);
    CheckA11yOverrides("item3", item_widget3, item_widget2, desk_widget);
    CheckA11yOverrides("desk", desk_widget, item_widget3, focus_widget);

    // Remove `window2`. The new order should be [focus_widget, item_widget1,
    // item_widget3, desk_widget, save_widget].
    window2.reset();
    CheckA11yOverrides("focus", focus_widget, desk_widget, item_widget1);
    CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget3);
    CheckA11yOverrides("item3", item_widget3, item_widget1, desk_widget);
    CheckA11yOverrides("desk", desk_widget, item_widget3, focus_widget);
    return;
  }

  SavedDeskSaveDeskButton* save_button = grid->GetSaveDeskForLaterButton();
  ASSERT_TRUE(save_button);
  views::Widget* save_widget = save_button->GetWidget();

  // Order should be [focus_widget, item_widget1, item_widget2, item_widget3,
  // desk_widget, save_widget].
  CheckA11yOverrides("focus", focus_widget, save_widget, item_widget1);
  CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget2);
  CheckA11yOverrides("item2", item_widget2, item_widget1, item_widget3);
  CheckA11yOverrides("item3", item_widget3, item_widget2, desk_widget);
  CheckA11yOverrides("desk", desk_widget, item_widget3, save_widget);
  CheckA11yOverrides("save", save_widget, desk_widget, focus_widget);

  // Remove `window2`. The new order should be [focus_widget, item_widget1,
  // item_widget3, desk_widget, save_widget].
  window2.reset();
  CheckA11yOverrides("focus", focus_widget, save_widget, item_widget1);
  CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget3);
  CheckA11yOverrides("item3", item_widget3, item_widget1, desk_widget);
  CheckA11yOverrides("desk", desk_widget, item_widget3, save_widget);
  CheckA11yOverrides("save", save_widget, desk_widget, focus_widget);
}

// Tests that accessibility overrides are set as expected on overview related
// widgets.
TEST_P(OverviewSessionTest, AccessibilityFocusAnnotatorNoSavedDesks) {
  // If saved desk is enabled, the a11y order changes. This is tested in
  // the saved desk test suite.
  if (DeskTemplatesOn() || saved_desk_util::ShouldShowSavedDesksOptions()) {
    return;
  }

  base::AutoReset<bool> disable =
      OverviewController::Get()->SetDisableAppIdCheckForTests();

  auto window3 = CreateAppWindow(gfx::Rect(100, 100));
  auto window2 = CreateAppWindow(gfx::Rect(100, 100));
  auto window1 = CreateAppWindow(gfx::Rect(100, 100));

  ToggleOverview();
  WaitForOverviewEnterAnimation();

  auto* focus_widget = views::Widget::GetWidgetForNativeWindow(
      GetOverviewSession()->GetOverviewFocusWindow());
  DCHECK(focus_widget);

  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
  auto* desk_widget = const_cast<views::Widget*>(grid->desks_widget());
  DCHECK(desk_widget);

  // Overview items are in MRU order, so the expected order in the grid list is
  // the reverse creation order.
  auto* item_widget1 = GetOverviewItemForWindow(window1.get())->item_widget();
  auto* item_widget2 = GetOverviewItemForWindow(window2.get())->item_widget();
  auto* item_widget3 = GetOverviewItemForWindow(window3.get())->item_widget();

  // Order should be [focus_widget, item_widget1, item_widget2, item_widget3,
  // desk_widget].
  CheckA11yOverrides("focus", focus_widget, desk_widget, item_widget1);
  CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget2);
  CheckA11yOverrides("item2", item_widget2, item_widget1, item_widget3);
  CheckA11yOverrides("item3", item_widget3, item_widget2, desk_widget);
  CheckA11yOverrides("desk", desk_widget, item_widget3, focus_widget);

  // Remove |window2|. The new order should be [focus_widget, item_widget1,
  // item_widget3, desk_widget].
  window2.reset();
  CheckA11yOverrides("focus", focus_widget, desk_widget, item_widget1);
  CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget3);
  CheckA11yOverrides("item3", item_widget3, item_widget1, desk_widget);
  CheckA11yOverrides("desk", desk_widget, item_widget3, focus_widget);
}

// Tests that removing a transient child during overview does not result in a
// crash when exiting overview.
TEST_P(OverviewSessionTest, RemoveTransientNoCrash) {
  auto child = CreateTestWindow();
  auto parent = CreateTestWindow();
  wm::AddTransientChild(parent.get(), child.get());

  ToggleOverview();
  wm::RemoveTransientChild(parent.get(), child.get());
  ToggleOverview();
}

// Tests that closing the overview item destroys the entire transient tree. Note
// that closing does not destroy transient children which are ShellSurfaceBase,
// but this test covers the regular case.
TEST_P(OverviewSessionTest, ClosingTransientTree) {
  // Release ownership as it will get deleted by the transient window manager,
  // when the associated overview item is closed later.
  auto* window = CreateAppWindow().release();

  auto* child_window1 = CreateAppWindow().release();
  wm::AddTransientChild(window, child_window1);

  // Add a second child that is not backed by a widget.
  auto* child_window2 = CreateTestWindow().release();
  wm::AddTransientChild(window, child_window2);

  TestDestroyedWidgetObserver widget_observer(
      views::Widget::GetWidgetForNativeWindow(window));
  TestDestroyedWidgetObserver child_widget_observer(
      views::Widget::GetWidgetForNativeWindow(child_window1));

  ToggleOverview();

  // There is a uaf that happens after adding a new desk and removing a desk,
  // which transfers all windows to the new desk, removes the OverviewItem for
  // the window and then adds a new `OverviewItem` for the window. We replicate
  // that over here. See crbug.com/1317875.
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  RemoveDesk(controller->active_desk(), DeskCloseType::kCombineDesks);

  OverviewItem* item =
      static_cast<OverviewItem*>(GetOverviewItemForWindow(window));
  ASSERT_TRUE(item);
  item->CloseWindow();

  // `NativeWidgetAura::Close()` fires a post task.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(widget_observer.widget_destroyed());
  EXPECT_TRUE(child_widget_observer.widget_destroyed());
}

// Tests that enabling or disabling ChromeVox works in overview mode. Regression
// test for b/270929836.
TEST_P(OverviewSessionTest, ToggleChromeVox) {
  ToggleOverview();
  PressAndReleaseKey(ui::VKEY_Z, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN);
  EXPECT_TRUE(
      Shell::Get()->accessibility_controller()->spoken_feedback().enabled());
  PressAndReleaseKey(ui::VKEY_Z, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN);
  EXPECT_FALSE(
      Shell::Get()->accessibility_controller()->spoken_feedback().enabled());
}

TEST_P(OverviewSessionTest, FrameThrottlingBrowser) {
  FrameThrottlingController* frame_throttling_controller =
      Shell::Get()->frame_throttling_controller();
  const int window_count = 5;
  std::vector<viz::FrameSinkId> ids{
      {1u, 1u}, {2u, 2u}, {3u, 3u}, {4u, 4u}, {5u, 5u}};
  std::vector<std::unique_ptr<aura::Window>> windows;
  windows.reserve(window_count + 1);
  for (int i = 0; i < window_count; ++i) {
    windows.emplace_back(
        CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect()));
    windows[i]->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::BROWSER);
    windows[i]->SetEmbedFrameSinkId(ids[i]);
  }

  ToggleOverview();
  EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
              testing::UnorderedElementsAreArray(ids));

  // Add a new window to overview.
  std::unique_ptr<aura::Window> new_window(
      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect()));
  constexpr viz::FrameSinkId new_window_id{6u, 6u};
  new_window->SetEmbedFrameSinkId(new_window_id);
  new_window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::BROWSER);
  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
  grid->AppendItem(new_window.get(), /*reposition=*/false, /*animate=*/false,
                   /*use_spawn_animation=*/false);
  windows.push_back(std::move(new_window));
  ids.push_back(new_window_id);
  EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
              testing::UnorderedElementsAreArray(ids));

  // Remove windows one by one.
  for (int i = 0; i < window_count + 1; ++i) {
    aura::Window* window = windows[i].get();
    ids.erase(ids.begin());
    auto* item = grid->GetOverviewItemContaining(window);
    grid->RemoveItem(item, /*item_destroying=*/false, /*reposition=*/false);
    EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
                testing::UnorderedElementsAreArray(ids));
  }
}

TEST_P(OverviewSessionTest, FrameThrottlingLacros) {
  FrameThrottlingController* frame_throttling_controller =
      Shell::Get()->frame_throttling_controller();
  const int window_count = 5;
  std::vector<viz::FrameSinkId> ids{
      {1u, 1u}, {2u, 2u}, {3u, 3u}, {4u, 4u}, {5u, 5u}};
  std::vector<std::unique_ptr<aura::Window>> windows;
  windows.reserve(window_count + 1);
  for (int i = 0; i < window_count; ++i) {
    windows.emplace_back(
        CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect()));
    windows[i]->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::LACROS);
    windows[i]->SetEmbedFrameSinkId(ids[i]);
  }
  for (auto& w : windows)
    EXPECT_FALSE(w->GetProperty(ash::kFrameRateThrottleKey));

  ToggleOverview();
  EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
              testing::UnorderedElementsAreArray(ids));
  for (auto& w : windows)
    EXPECT_TRUE(w->GetProperty(ash::kFrameRateThrottleKey));

  // Add a new window to overview.
  std::unique_ptr<aura::Window> new_window(
      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect()));
  constexpr viz::FrameSinkId new_window_id{6u, 6u};
  new_window->SetEmbedFrameSinkId(new_window_id);
  new_window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::LACROS);
  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
  grid->AppendItem(new_window.get(), /*reposition=*/false, /*animate=*/false,
                   /*use_spawn_animation=*/false);
  windows.push_back(std::move(new_window));
  ids.push_back(new_window_id);
  EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
              testing::UnorderedElementsAreArray(ids));
  for (auto& w : windows)
    EXPECT_TRUE(w->GetProperty(ash::kFrameRateThrottleKey));

  // Remove windows one by one.
  for (int i = 0; i < window_count + 1; ++i) {
    aura::Window* window = windows[i].get();
    ids.erase(ids.begin());
    auto* item = grid->GetOverviewItemContaining(window);
    grid->RemoveItem(item, /*item_destroying=*/false, /*reposition=*/false);
    EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
                testing::UnorderedElementsAreArray(ids));
    EXPECT_FALSE(window->GetProperty(ash::kFrameRateThrottleKey));
  }
}

TEST_P(OverviewSessionTest, FrameThrottlingArc) {
  testing::NiceMock<MockFrameThrottlingObserver> observer;
  FrameThrottlingController* frame_throttling_controller =
      Shell::Get()->frame_throttling_controller();
  frame_throttling_controller->AddArcObserver(&observer);

  const int window_count = 5;
  std::vector<std::unique_ptr<aura::Window>> windows;
  windows.reserve(window_count + 1);
  for (int i = 0; i < window_count; ++i) {
    windows.emplace_back(
        CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect()));
    windows[i]->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::ARC_APP);
  }

  auto windows_to_throttle =
      base::ToVector(windows, &std::unique_ptr<aura::Window>::get);
  EXPECT_CALL(observer,
              OnThrottlingStarted(
                  testing::UnorderedElementsAreArray(windows_to_throttle),
                  frame_throttling_controller->GetCurrentThrottledFrameRate()));
  ToggleOverview();

  // Add a new window to overview.
  std::unique_ptr<aura::Window> new_window(
      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect()));
  new_window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::ARC_APP);
  windows_to_throttle.push_back(new_window.get());
  EXPECT_CALL(observer, OnThrottlingEnded());
  EXPECT_CALL(observer,
              OnThrottlingStarted(
                  testing::UnorderedElementsAreArray(windows_to_throttle),
                  frame_throttling_controller->GetCurrentThrottledFrameRate()));
  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
  grid->AppendItem(new_window.get(), /*reposition=*/false, /*animate=*/false,
                   /*use_spawn_animation=*/false);
  windows.push_back(std::move(new_window));

  // Remove windows one by one. Once one window is out of the overview grid, no
  // more windows will be throttled.
  for (int i = 0; i < window_count + 1; ++i) {
    aura::Window* window = windows[i].get();
    if (i == 0)
      EXPECT_CALL(observer, OnThrottlingEnded());
    EXPECT_CALL(observer, OnThrottlingStarted(testing::_, testing::_)).Times(0);
    auto* item = grid->GetOverviewItemContaining(window);
    grid->RemoveItem(item, /*item_destroying=*/false, /*reposition=*/false);
  }
  frame_throttling_controller->RemoveArcObserver(&observer);
}

// Tests that if we combine a desk in overview, the overview applied clipping is
// removed properly (other portions of the window will not be visible on exiting
// overview). Regression test for http://b/282010852.
TEST_P(OverviewSessionTest, WindowClippingAfterCombiningDesks) {
  // Need at least two desks to combine them.
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);

  // Overview clip is used to apply an animation to remove the normal header and
  // keep it hidden during overview. So we need a non-zero top inset to
  // reproduce the bug.
  auto normal_window = CreateAppWindow();
  normal_window->SetProperty(aura::client::kTopViewInset, 32);
  ASSERT_TRUE(normal_window->layer()->clip_rect().IsEmpty());

  ui::ScopedAnimationDurationScaleMode scale_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  ToggleOverview();
  WaitForOverviewEnterAnimation();
  ASSERT_FALSE(normal_window->layer()->clip_rect().IsEmpty());

  // Combine the two desks while inside overview.
  RemoveDesk(controller->active_desk(), DeskCloseType::kCombineDesks);
  ui::LayerAnimationStoppedWaiter().Wait(normal_window->layer());

  // Tests that on exiting overview, the clip is removed.
  ToggleOverview();
  WaitForOverviewExitAnimation();
  EXPECT_TRUE(normal_window->layer()->clip_rect().IsEmpty());
}

// Tests that if we tab while the desks bar is sliding out, there is no crash.
// Regression test for http://b/302708219.
TEST_P(OverviewSessionTest, TabbingDuringExitAnimation) {
  ui::ScopedAnimationDurationScaleMode scale_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  ToggleOverview();
  WaitForOverviewEnterAnimation();

  auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
  const views::Widget* desks_widget = overview_grid->desks_widget();

  // First activate the desks bar by clicking it (but do not click on the desk
  // preview because that will exit overview). See bug details for why we need
  // to activate the desks bar first.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      desks_widget->GetWindowBoundsInScreen().origin());
  generator->ClickLeftButton();
  ASSERT_TRUE(wm::IsActiveWindow(desks_widget->GetNativeWindow()));

  // Exit overview. This will slide out the desks widget.
  ToggleOverview();

  // Try tab focus traversal while the animation is in progress. There should be
  // no crash.
  PressAndReleaseKey(ui::VKEY_TAB);
  PressAndReleaseKey(ui::VKEY_TAB);
  PressAndReleaseKey(ui::VKEY_TAB);
}

TEST_P(OverviewSessionTest,
       OcclusionUpdatedOnOverviewToggleForVirtualDeskPreviewsSingleWindow) {
  using OcclusionState = aura::Window::OcclusionState;

  // We don't need to worry about virtual desk previews not showing up if we
  // have snapshots, so this test tests the case where we don't have snapshots.
  if (SnapshotOn()) {
    GTEST_SKIP();
  }
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // First ensure there are two desks.
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  ASSERT_EQ(2u, controller->desks().size());

  Desk* desk1 = controller->desks()[0].get();
  Desk* desk2 = controller->desks()[1].get();

  // Create one window on an inactive desk.
  std::unique_ptr<aura::Window> window(CreateAppWindow(gfx::Rect(100, 100)));
  controller->SendToDeskAtIndex(window.get(), 1);
  EXPECT_TRUE(base::Contains(desk2->windows(), window.get()));
  EXPECT_TRUE(desk1->is_active());
  window->TrackOcclusionState();

  // Window should be hidden on an inactive desk.
  EXPECT_EQ(OcclusionState::HIDDEN, window->GetOcclusionState());

  // Enter overview mode.
  ToggleOverview();

  // Window should immediately be marked as visible.
  EXPECT_EQ(OcclusionState::VISIBLE, window->GetOcclusionState());
  WaitForOverviewEnterAnimation();

  // Window should stay visible.
  EXPECT_EQ(OcclusionState::VISIBLE, window->GetOcclusionState());

  // Exit overview mode.
  ToggleOverview();

  // Window should still be visible until the animation finishes.
  EXPECT_EQ(OcclusionState::VISIBLE, window->GetOcclusionState());
  WaitForOverviewExitAnimation();

  // Overview mode pauses occlusion on exit for a while, so wait for this state.
  WaitForOcclusionStateChange(window.get(), OcclusionState::HIDDEN);
}

TEST_P(OverviewSessionTest,
       OcclusionUpdatedOnOverviewToggleForVirtualDeskPreviewsTwoWindows) {
  using OcclusionState = aura::Window::OcclusionState;

  // We don't need to worry about virtual desk previews not showing up if we
  // have snapshots, so this test tests the case where we don't have snapshots.
  if (SnapshotOn()) {
    GTEST_SKIP();
  }
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // First ensure there are two desks.
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  ASSERT_EQ(2u, controller->desks().size());

  Desk* desk1 = controller->desks()[0].get();
  Desk* desk2 = controller->desks()[1].get();

  // Create one window on the active desk.
  std::unique_ptr<aura::Window> window1(CreateAppWindow(gfx::Rect(100, 100)));
  controller->SendToDeskAtIndex(window1.get(), 0);
  EXPECT_TRUE(base::Contains(desk1->windows(), window1.get()));
  window1->TrackOcclusionState();

  // Create one window on an inactive desk.
  std::unique_ptr<aura::Window> window2(CreateAppWindow(gfx::Rect(100, 100)));
  controller->SendToDeskAtIndex(window2.get(), 1);
  EXPECT_TRUE(base::Contains(desk2->windows(), window2.get()));
  EXPECT_TRUE(desk1->is_active());
  window2->TrackOcclusionState();

  // `window2` should be hidden on an inactive desk.
  EXPECT_EQ(OcclusionState::VISIBLE, window1->GetOcclusionState());
  EXPECT_EQ(OcclusionState::HIDDEN, window2->GetOcclusionState());

  // Enter overview mode.
  ToggleOverview();

  // `window2` will not immediately be marked as visible, because the desks
  // widget is only shown after the animation finishes.
  EXPECT_EQ(OcclusionState::VISIBLE, window1->GetOcclusionState());
  WaitForOverviewEnterAnimation();

  // `window2` should stay visible.
  EXPECT_EQ(OcclusionState::VISIBLE, window1->GetOcclusionState());
  EXPECT_EQ(OcclusionState::VISIBLE, window2->GetOcclusionState());

  // Exit overview mode.
  ToggleOverview();

  // `window2` should still be visible until the animation finishes.
  EXPECT_EQ(OcclusionState::VISIBLE, window1->GetOcclusionState());
  EXPECT_EQ(OcclusionState::VISIBLE, window2->GetOcclusionState());
  WaitForOverviewExitAnimation();

  // Overview mode pauses occlusion on exit for a while, so wait for this state.
  WaitForOcclusionStateChange(window2.get(), OcclusionState::HIDDEN);
  EXPECT_EQ(OcclusionState::VISIBLE, window1->GetOcclusionState());
}

// Verify the following behavior when dragging an `OverviewItem` to the new desk
// button on a different display:
// 1. The new desk button on the target display changes to
// `DeskIconButton::State::kActive`.
// 2. New desk buttons on other displays remain in
// `DeskIconButton::State::kExpanded`.
// 3. Upon dropping the OverviewItem, all new desk buttons (including the target
// display) are restored to `DeskIconButton::State::kExpanded` state.
TEST_P(OverviewSessionTest, NewDeskButtonStateUpdateOnMultiDisplay) {
  auto skip_scale_up_new_desk_button_duration = OverviewWindowDragController::
      SkipNewDeskButtonScaleUpDurationForTesting();

  UpdateDisplay("800x700,801+0-800x700");
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  const auto& displays = display_manager->active_display_list();
  ASSERT_EQ(2U, displays.size());

  const gfx::Point point_in_display1(502, 300);
  ASSERT_TRUE(displays[0].bounds().Contains(point_in_display1));
  ASSERT_FALSE(displays[1].bounds().Contains(point_in_display1));

  std::unique_ptr<aura::Window> window =
      CreateAppWindow(gfx::Rect(10, 10, 200, 100));
  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());
  ASSERT_TRUE(IsWindowInItsCorrespondingOverviewGrid(window.get()));

  // Verify that the new desk buttons on both displays have
  // `DeskIconButton::State::kZero` state initially.
  const auto& grids = GetOverviewSession()->grid_list();
  ASSERT_EQ(2u, grids.size());
  auto* grid0 = grids[0].get();
  ASSERT_TRUE(grid0);
  auto* desks_bar_view0 = grid0->desks_bar_view();
  const DeskIconButton* new_desk_button0 = desks_bar_view0->new_desk_button();
  ASSERT_TRUE(new_desk_button0);
  ASSERT_TRUE(new_desk_button0->GetVisible());
  ASSERT_EQ(DeskIconButton::State::kZero, new_desk_button0->state());

  auto* grid1 = grids[1].get();
  ASSERT_TRUE(grid1);
  auto* desks_bar_view1 = grid1->desks_bar_view();
  const DeskIconButton* new_desk_button1 = desks_bar_view1->new_desk_button();
  ASSERT_TRUE(new_desk_button1);
  ASSERT_TRUE(new_desk_button1->GetVisible());
  ASSERT_EQ(DeskIconButton::State::kZero, new_desk_button1->state());

  OverviewItemBase* overview_item = GetOverviewItemForWindow(window.get());
  ASSERT_TRUE(overview_item);

  // Drag the `overview_item` to new desk button on display #2 w/o releasing the
  // mouse. Verify that the new desk button on display #2 turns into
  // `DeskIconButton::State::kActive` state.
  auto* event_generator = GetEventGenerator();
  DragItemToPoint(overview_item,
                  new_desk_button1->GetBoundsInScreen().CenterPoint(),
                  event_generator, /*by_touch_gestures=*/false, /*drop=*/false);
  EXPECT_EQ(DeskIconButton::State::kExpanded, new_desk_button0->state());
  EXPECT_EQ(DeskIconButton::State::kActive, new_desk_button1->state());

  // Drag the `overview_item` back to display #1 w/o and drop. Verify that the
  // new desk buttons on all displays are restored to
  // `DeskIconButton::State::kExpanded` state.
  DragItemToPoint(overview_item, point_in_display1, event_generator,
                  /*by_touch_gestures=*/false, /*drop=*/true);
  EXPECT_EQ(DeskIconButton::State::kExpanded, new_desk_button0->state());
  EXPECT_EQ(DeskIconButton::State::kExpanded, new_desk_button1->state());
}

// Verify that when an overview item is moved to a different display, it
// is properly removed from the original grid and displayed in the new one with
// no crash. See original crash reported at http://b/320479135.
TEST_P(OverviewSessionTest,
       NoCrashWhenSettingOverviewItemBoundsOnAnotherDisplay) {
  UpdateDisplay("800x700,801+0-800x700");
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  EXPECT_EQ(2U, display_manager->GetNumDisplays());
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();

  std::unique_ptr<aura::Window> window =
      CreateAppWindow(gfx::Rect(10, 10, 200, 100));
  // Explicitly call `set_allow_set_bounds_direct()` to true to trigger the same
  // stack trace.
  WindowState::Get(window.get())->set_allow_set_bounds_direct(true);
  aura::Window* old_root_window = window->GetRootWindow();

  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());

  const auto& grids = GetOverviewSession()->grid_list();
  ASSERT_EQ(2u, grids.size());
  auto* grid0 = grids[0].get();
  ASSERT_TRUE(grid0);
  const auto& overview_items = grid0->item_list();
  ASSERT_EQ(overview_items.size(), 1u);
  EXPECT_TRUE(IsWindowInItsCorrespondingOverviewGrid(window.get()));

  // Verify that when setting the window bounds to another display, the window
  // will be moved properly.
  window->SetBoundsInScreen(
      gfx::Rect(900, 10, 200, 100),
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          Shell::GetAllRootWindows()[1].get()));
  EXPECT_NE(window->GetRootWindow(), old_root_window);
  EXPECT_TRUE(IsWindowInItsCorrespondingOverviewGrid(window.get()));
}

// Used to replicate the behavior of the Crostini app window, which would set
// the window bounds to its registered display on the window's visibility
// changed. See
// `AppServiceAppWindowCrostiniTracker::OnWindowVisibilityChanged()` for more
// details.
class CrostiniWindowVisibilityObserver : public aura::WindowObserver {
 public:
  explicit CrostiniWindowVisibilityObserver(aura::Window* window)
      : window_(window) {
    window->AddObserver(this);
  }

  ~CrostiniWindowVisibilityObserver() override {
    window_->RemoveObserver(this);
  }

  // aura::WindowObserver:
  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
    if (visible) {
      auto current_display =
          display::Screen::GetScreen()->GetDisplayNearestWindow(window);
      const auto dst_display =
          display::Screen::GetScreen()->GetPrimaryDisplay();
      window->SetBoundsInScreen(
          gfx::Rect(dst_display.bounds().origin(), window->bounds().size()),
          dst_display);
    }
  }

 private:
  raw_ptr<aura::Window> window_;
};

// Test verifies that dragging a minimized Crostini window to an external
// display in Overview mode and then clicking to activate it doesn't cause a
// crash. The crash would typically occur due to the
// `AppServiceAppWindowCrostiniTracker` attempting to move the window back to
// its registered display, which triggers a `CHECK_EQ(root_window_,
// window->GetRootWindow())` crash in `OverviewItem::SetItemBounds()`. See
// http://b/334911238 for more details.
TEST_P(OverviewSessionTest,
       NoCrashWhenSettingMinimizedOverviewItemBoundsOnAnotherDisplay) {
  UpdateDisplay("1410x940,1411+0-2560x1440");
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  EXPECT_EQ(2U, display_manager->GetNumDisplays());
  const auto& displays = display_manager->active_display_list();
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();

  const gfx::Point point_in_display2(2500, 500);
  ASSERT_FALSE(displays[0].bounds().Contains(point_in_display2));
  ASSERT_TRUE(displays[1].bounds().Contains(point_in_display2));

  std::unique_ptr<aura::Window> window(
      CreateAppWindow(gfx::Rect(10, 10, 500, 300)));

  WMEvent minimize_event(WM_EVENT_MINIMIZE);
  WindowState::Get(window.get())->OnWMEvent(&minimize_event);
  ASSERT_TRUE(WindowState::Get(window.get())->IsMinimized());
  EXPECT_FALSE(window->IsVisible());
  EXPECT_EQ(0.f, window->layer()->GetTargetOpacity());

  CrostiniWindowVisibilityObserver visibility_observer(window.get());

  ToggleOverview();
  WaitForOverviewEntered();
  ASSERT_TRUE(IsInOverviewSession());

  const auto& grids = GetOverviewSession()->grid_list();
  ASSERT_EQ(2u, grids.size());
  auto grid0 = grids[0].get();
  ASSERT_TRUE(grid0);
  const auto& overview_items = grid0->item_list();
  ASSERT_EQ(overview_items.size(), 1u);
  EXPECT_TRUE(IsWindowInItsCorrespondingOverviewGrid(window.get()));

  auto* event_generator = GetEventGenerator();
  auto* overview_item = overview_items[0].get();
  ASSERT_TRUE(overview_item);
  DragItemToPoint(overview_item, point_in_display2, event_generator,
                  /*by_touch_gestures=*/false, /*drop=*/true);
  EXPECT_TRUE(IsWindowInItsCorrespondingOverviewGrid(window.get()));

  // Verify that the windows are moved to the `displays[1]` properly.
  display::Screen* screen = display::Screen::GetScreen();
  EXPECT_EQ(displays[1].id(),
            screen->GetDisplayNearestWindow(window.get()).id());

  event_generator->set_current_screen_location(gfx::ToRoundedPoint(
      GetOverviewItemForWindow(window.get())->target_bounds().CenterPoint()));

  // Verify that there will be no crash when activating the minimized Crostini
  // window.
  event_generator->ClickLeftButton();
}

TEST_P(OverviewSessionTest, OverviewItemViewAccessibleProperties) {
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  wm::ActivateWindow(window.get());
  ToggleOverview();
  auto* overview_item_view =
      static_cast<OverviewItem*>(GetOverviewItemForWindow(window.get()))
          ->overview_item_view();
  ui::AXNodeData data;

  ASSERT_TRUE(overview_item_view);
  overview_item_view->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.role, ax::mojom::Role::kGenericContainer);
  EXPECT_EQ(overview_item_view->GetViewAccessibility().GetCachedDescription(),
            l10n_util::GetStringUTF16(
                IDS_ASH_OVERVIEW_CLOSABLE_HIGHLIGHT_ITEM_A11Y_EXTRA_TIP));
}

// If you update the parameterisation of OverviewSessionTest also update the
// parameterisation of OverviewRasterScaleTest below.
INSTANTIATE_TEST_SUITE_P(
    /*no prefix*/,
    OverviewSessionTest,
    testing::Combine(testing::Bool(), testing::Bool()),
    OverviewSessionTestParamsToString);

class OverviewRasterScaleTest : public OverviewSessionTest {
 public:
  OverviewRasterScaleTest() = default;
  OverviewRasterScaleTest(const OverviewRasterScaleTest&) = delete;
  OverviewRasterScaleTest& operator=(const OverviewRasterScaleTest&) = delete;
  ~OverviewRasterScaleTest() override = default;

  // OverviewSessionTest:
  void SetUp() override {
    OverviewSessionTest::SetUp();

    Shell::Get()
        ->raster_scale_controller()
        ->set_raster_scale_slop_proportion_for_testing(0.0f);
    Shell::Get()
        ->overview_controller()
        ->set_occlusion_pause_duration_for_start_for_test(
            base::Milliseconds(0));
    Shell::Get()
        ->overview_controller()
        ->set_occlusion_pause_duration_for_end_for_test(base::Milliseconds(0));
  }

  float ExpectedRasterScale(aura::Window* window,
                            gfx::Rect start_bounds,
                            bool window_grows) {
    auto* item = GetOverviewItemForWindow(window);
    CHECK(item);

    // If the window is minimized, the widget size is changed. Otherwise, it's
    // transformed via the transform window. Use the target bounds if it's not
    // minimized. If it's minimized, it won't have its size animated so it's
    // safe to look at the item view size.
    auto end_bounds = window->layer()->GetTargetTransform().MapRect(
        gfx::RectF(window->GetTargetBounds()));

    if (WindowState::Get(window)->IsMinimized()) {
      const auto insets = gfx::Insets::TLBR(
          window->GetProperty(aura::client::kTopViewInset), 0, 0, 0);
      start_bounds.Inset(insets);
      const auto size = item->GetLeafItemForWindow(window)
                            ->overview_item_view()
                            ->GetPreviewViewSize();
      end_bounds = gfx::RectF(gfx::Rect(size));
    }

    auto transform = gfx::TransformBetweenRects(gfx::RectF(start_bounds),
                                                gfx::RectF(end_bounds));
    auto scale_2d = transform.To2dScale();
    auto scale = std::max(scale_2d.x(), scale_2d.y());
    // Specify 1.0's manually, since they are easy to know, and we want to
    // minimize the amount of extra computation for raster scale expectations.
    EXPECT_NE(1.0f, scale);
    if (window_grows) {
      EXPECT_GT(scale, 1.0);
    } else {
      EXPECT_LT(scale, 1.0);
    }
    return scale;
  }

  void MinimizeAndCheckWindow(aura::Window* window) {
    WMEvent minimize_event(WM_EVENT_MINIMIZE);
    WindowState::Get(window)->OnWMEvent(&minimize_event);
    EXPECT_FALSE(window->IsVisible());
    EXPECT_EQ(0.f, window->layer()->GetTargetOpacity());
    ASSERT_TRUE(WindowState::Get(window)->IsMinimized());
  }

  void MaximizeAndCheckWindow(aura::Window* window) {
    WMEvent maximize_event(WM_EVENT_MAXIMIZE);
    WindowState::Get(window)->OnWMEvent(&maximize_event);
    EXPECT_TRUE(window->IsVisible());
    ASSERT_TRUE(WindowState::Get(window)->IsMaximized());
  }
};

// Tests raster scale changes for a single window which grows when entering
// overview mode.
TEST_P(OverviewRasterScaleTest,
       RasterScaleAnimatedSingleWindowEnterGrowExitShrink) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(kInitWindowBoundsToGrow));
  auto tracker = RasterScaleChangeTracker(window.get());

  gfx::Rect start_bounds = GetTransformedTargetBounds(window.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  ToggleOverview();

  // Since the window gets larger, we need to use the more detailed raster
  // before the animation starts.
  auto raster_scale =
      ExpectedRasterScale(window.get(), start_bounds, /*window_grows=*/true);
  EXPECT_EQ(std::vector<float>{raster_scale}, tracker.TakeRasterScaleChanges());
  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  // No change after animation.
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());

  ToggleOverview();

  // Expect no raster scale change as we need to keep the higher detail during
  // the shrink animation.
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  // After completion, restore to normal raster scale.
  EXPECT_EQ(std::vector<float>{1.0}, tracker.TakeRasterScaleChanges());
}

// Tests raster scale changes for a single window which shrinks when entering
// overview mode.
TEST_P(OverviewRasterScaleTest,
       RasterScaleAnimatedSingleWindowEnterShrinkExitGrow) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(kInitWindowBoundsToShrink));
  auto tracker = RasterScaleChangeTracker(window.get());

  gfx::Rect start_bounds = GetTransformedTargetBounds(window.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  ToggleOverview();

  // Since the window gets smaller, we need to keep the more detailed raster
  // until after the animation finishes.
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  auto raster_scale =
      ExpectedRasterScale(window.get(), start_bounds, /*window_grows=*/false);
  EXPECT_EQ(std::vector<float>{raster_scale}, tracker.TakeRasterScaleChanges());

  ToggleOverview();

  // Expect a raster scale change as we need to use the higher detail during
  // the grow animation.
  EXPECT_EQ(std::vector<float>{1.0}, tracker.TakeRasterScaleChanges());
  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
}

// Tests raster scale changes for a minimized single window which grows when
// entering overview mode.
TEST_P(OverviewRasterScaleTest,
       RasterScaleMinimizedSingleWindowEnterGrowExitShrink) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(kInitWindowBoundsToGrow));
  auto tracker = RasterScaleChangeTracker(window.get());

  gfx::Rect start_bounds = GetTransformedTargetBounds(window.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  MinimizeAndCheckWindow(window.get());

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  ToggleOverview();

  // Since the window will be shown larger immediately, change raster scale
  // immediately.
  auto raster_scale =
      ExpectedRasterScale(window.get(), start_bounds, /*window_grows=*/true);
  EXPECT_EQ(std::vector<float>{raster_scale}, tracker.TakeRasterScaleChanges());
  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  // No change after enter.
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());

  ToggleOverview();

  // Expect no raster scale change as we need to keep the higher detail until
  // everything is hidden.
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  // After completion, restore to normal raster scale.
  EXPECT_EQ(std::vector<float>{1.0}, tracker.TakeRasterScaleChanges());
}

// Tests raster scale changes for a minimized single window which shrinks when
// entering overview mode.
TEST_P(OverviewRasterScaleTest,
       RasterScaleMinimizedSingleWindowEnterShrinkExitGrow) {
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(kInitWindowBoundsToShrink));
  auto tracker = RasterScaleChangeTracker(window.get());

  gfx::Rect start_bounds = GetTransformedTargetBounds(window.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  MinimizeAndCheckWindow(window.get());

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  ToggleOverview();

  // Since the window is minimized, it won't be animated and we can show the
  // less detailed version immediately.
  auto raster_scale =
      ExpectedRasterScale(window.get(), start_bounds, /*window_grows=*/false);
  EXPECT_EQ(std::vector<float>{raster_scale}, tracker.TakeRasterScaleChanges());
  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  // No change after enter.
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());

  ToggleOverview();

  // Expect no raster scale change as that will be more performant to keep lower
  // detail.
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  // After completion, restore to normal raster scale.
  EXPECT_EQ(std::vector<float>{1.0}, tracker.TakeRasterScaleChanges());
}

// Tests raster scale changes for a more complex case with multiple windows in
// different states.
TEST_P(OverviewRasterScaleTest, RasterScaleMultipleWindows) {
  std::unique_ptr<aura::Window> window_grow_animated(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_animated(
      CreateTestWindow(kInitWindowBoundsToShrink));
  std::unique_ptr<aura::Window> window_grow_minimized(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_minimized(
      CreateTestWindow(kInitWindowBoundsToShrink));

  MinimizeAndCheckWindow(window_grow_minimized.get());
  MinimizeAndCheckWindow(window_shrink_minimized.get());

  auto tracker_grow_animated =
      RasterScaleChangeTracker(window_grow_animated.get());
  auto tracker_shrink_animated =
      RasterScaleChangeTracker(window_shrink_animated.get());
  auto tracker_grow_minimized =
      RasterScaleChangeTracker(window_grow_minimized.get());
  auto tracker_shrink_minimized =
      RasterScaleChangeTracker(window_shrink_minimized.get());

  gfx::Rect start_bounds_grow_animated =
      GetTransformedTargetBounds(window_grow_animated.get());
  gfx::Rect start_bounds_shrink_animated =
      GetTransformedTargetBounds(window_shrink_animated.get());
  gfx::Rect start_bounds_grow_minimized =
      GetTransformedTargetBounds(window_grow_minimized.get());
  gfx::Rect start_bounds_shrink_minimized =
      GetTransformedTargetBounds(window_shrink_minimized.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_grow_animated =
      ExpectedRasterScale(window_grow_animated.get(),
                          start_bounds_grow_animated, /*window_grows=*/true);
  float raster_scale_shrink_animated =
      ExpectedRasterScale(window_shrink_animated.get(),
                          start_bounds_shrink_animated, /*window_grows=*/false);
  float raster_scale_grow_minimized =
      ExpectedRasterScale(window_grow_minimized.get(),
                          start_bounds_grow_minimized, /*window_grows=*/true);
  float raster_scale_shrink_minimized = ExpectedRasterScale(
      window_shrink_minimized.get(), start_bounds_shrink_minimized,
      /*window_grows=*/false);

  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  ToggleOverview();

  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_minimized.TakeRasterScaleChanges());
}

// Tests raster scale changes when a maximized window exists with windows on
// top.
TEST_P(OverviewRasterScaleTest, RasterScaleMaximizedWithGrowingRestoredOnTop) {
  std::unique_ptr<aura::Window> window_maximized(
      CreateTestWindow(gfx::Rect(100, 100)));
  std::unique_ptr<aura::Window> window_grow(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink(
      CreateTestWindow(kInitWindowBoundsToShrink));

  MaximizeAndCheckWindow(window_maximized.get());

  window_maximized->parent()->StackChildAtTop(window_grow.get());
  window_maximized->parent()->StackChildAtTop(window_shrink.get());

  auto tracker_maximized = RasterScaleChangeTracker(window_maximized.get());
  auto tracker_grow = RasterScaleChangeTracker(window_grow.get());
  auto tracker_shrink = RasterScaleChangeTracker(window_shrink.get());

  gfx::Rect start_bounds_maximized =
      GetTransformedTargetBounds(window_maximized.get());
  gfx::Rect start_bounds_grow = GetTransformedTargetBounds(window_grow.get());
  gfx::Rect start_bounds_shrink =
      GetTransformedTargetBounds(window_shrink.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_maximized = ExpectedRasterScale(
      window_maximized.get(), start_bounds_maximized, /*window_grows=*/false);
  float raster_scale_grow = ExpectedRasterScale(
      window_grow.get(), start_bounds_grow, /*window_grows=*/true);
  float raster_scale_shrink = ExpectedRasterScale(
      window_shrink.get(), start_bounds_shrink, /*window_grows=*/false);

  // Maximized needs to keep detail while it shrinks.
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow},
            tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink},
            tracker_shrink.TakeRasterScaleChanges());

  ToggleOverview();

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0}, tracker_shrink.TakeRasterScaleChanges());

  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0}, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink.TakeRasterScaleChanges());
}

// Tests raster scale changes when a maximized window exists with windows below.
TEST_P(OverviewRasterScaleTest, RasterScaleMaximizedWithGrowingRestoredBelow) {
  std::unique_ptr<aura::Window> window_grow(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink(
      CreateTestWindow(kInitWindowBoundsToShrink));
  std::unique_ptr<aura::Window> window_maximized(
      CreateTestWindow(gfx::Rect(100, 100)));

  MaximizeAndCheckWindow(window_maximized.get());

  window_maximized->parent()->StackChildAtBottom(window_grow.get());
  window_maximized->parent()->StackChildAtBottom(window_shrink.get());

  auto tracker_maximized = RasterScaleChangeTracker(window_maximized.get());
  auto tracker_grow = RasterScaleChangeTracker(window_grow.get());
  auto tracker_shrink = RasterScaleChangeTracker(window_shrink.get());

  gfx::Rect start_bounds_maximized =
      GetTransformedTargetBounds(window_maximized.get());
  gfx::Rect start_bounds_grow = GetTransformedTargetBounds(window_grow.get());
  gfx::Rect start_bounds_shrink =
      GetTransformedTargetBounds(window_shrink.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_maximized = ExpectedRasterScale(
      window_maximized.get(), start_bounds_maximized, /*window_grows=*/false);
  float raster_scale_grow = ExpectedRasterScale(
      window_grow.get(), start_bounds_grow, /*window_grows=*/true);
  float raster_scale_shrink = ExpectedRasterScale(
      window_shrink.get(), start_bounds_shrink, /*window_grows=*/false);

  // Both windows are covered, so they can have their final raster scale applied
  // immediately.
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow},
            tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink},
            tracker_shrink.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink.TakeRasterScaleChanges());

  ToggleOverview();

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink.TakeRasterScaleChanges());

  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  // Windows will be covered and not animate, so they wait until the animation
  // has finished to update.
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0}, tracker_grow.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0}, tracker_shrink.TakeRasterScaleChanges());
}

// Tests raster scale changes for a more complex case with multiple windows in
// different states when the overview mode animation is cancelled while entering
// and exiting.
TEST_P(OverviewRasterScaleTest, RasterScaleMultipleWindowsCancel) {
  std::unique_ptr<aura::Window> window_grow_covered(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_covered(
      CreateTestWindow(kInitWindowBoundsToShrink));
  std::unique_ptr<aura::Window> window_maximized(
      CreateTestWindow(gfx::Rect(100, 100)));
  std::unique_ptr<aura::Window> window_grow_animated(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_animated(
      CreateTestWindow(kInitWindowBoundsToShrink));
  std::unique_ptr<aura::Window> window_grow_minimized(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_minimized(
      CreateTestWindow(kInitWindowBoundsToShrink));

  MinimizeAndCheckWindow(window_grow_minimized.get());
  MinimizeAndCheckWindow(window_shrink_minimized.get());

  MaximizeAndCheckWindow(window_maximized.get());

  window_maximized->parent()->StackChildAtBottom(window_grow_covered.get());
  window_maximized->parent()->StackChildAtBottom(window_shrink_covered.get());
  window_maximized->parent()->StackChildAtTop(window_shrink_animated.get());
  window_maximized->parent()->StackChildAtTop(window_grow_animated.get());

  window_grow_animated->Focus();

  auto tracker_grow_covered =
      RasterScaleChangeTracker(window_grow_covered.get());
  auto tracker_shrink_covered =
      RasterScaleChangeTracker(window_shrink_covered.get());
  auto tracker_maximized = RasterScaleChangeTracker(window_maximized.get());
  auto tracker_grow_animated =
      RasterScaleChangeTracker(window_grow_animated.get());
  auto tracker_shrink_animated =
      RasterScaleChangeTracker(window_shrink_animated.get());
  auto tracker_grow_minimized =
      RasterScaleChangeTracker(window_grow_minimized.get());
  auto tracker_shrink_minimized =
      RasterScaleChangeTracker(window_shrink_minimized.get());

  gfx::Rect start_bounds_grow_covered =
      GetTransformedTargetBounds(window_grow_covered.get());
  gfx::Rect start_bounds_shrink_covered =
      GetTransformedTargetBounds(window_shrink_covered.get());
  gfx::Rect start_bounds_maximized =
      GetTransformedTargetBounds(window_maximized.get());
  gfx::Rect start_bounds_grow_animated =
      GetTransformedTargetBounds(window_grow_animated.get());
  gfx::Rect start_bounds_shrink_animated =
      GetTransformedTargetBounds(window_shrink_animated.get());
  gfx::Rect start_bounds_grow_minimized =
      GetTransformedTargetBounds(window_grow_minimized.get());
  gfx::Rect start_bounds_shrink_minimized =
      GetTransformedTargetBounds(window_shrink_minimized.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_grow_covered =
      ExpectedRasterScale(window_grow_covered.get(), start_bounds_grow_covered,
                          /*window_grows=*/true);
  float raster_scale_shrink_covered = ExpectedRasterScale(
      window_shrink_covered.get(), start_bounds_shrink_covered,
      /*window_grows=*/false);
  float raster_scale_maximized =
      ExpectedRasterScale(window_maximized.get(), start_bounds_maximized,
                          /*window_grows=*/false);
  float raster_scale_grow_animated =
      ExpectedRasterScale(window_grow_animated.get(),
                          start_bounds_grow_animated, /*window_grows=*/true);
  float raster_scale_shrink_animated = ExpectedRasterScale(
      window_shrink_animated.get(), start_bounds_shrink_animated,
      /*window_grows=*/false);
  float raster_scale_grow_minimized =
      ExpectedRasterScale(window_grow_minimized.get(),
                          start_bounds_grow_minimized, /*window_grows=*/true);
  float raster_scale_shrink_minimized = ExpectedRasterScale(
      window_shrink_minimized.get(), start_bounds_shrink_minimized,
      /*window_grows=*/false);

  EXPECT_EQ(std::vector<float>{raster_scale_grow_covered},
            tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_covered},
            tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  // Cancel overview mode by focusing another window.
  EXPECT_TRUE(InOverviewSession());
  window_shrink_animated->Focus();
  EXPECT_FALSE(InOverviewSession());

  // Animation will start to reverse, no reason to change the raster scale here.
  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  // Enter overview mode so we can test cancelling exit.
  ToggleOverview();

  EXPECT_EQ(std::vector<float>{raster_scale_grow_covered},
            tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_covered},
            tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  // In overview mode. Start exiting and then cancel.
  ToggleOverview();

  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  // Cancel leaving overview mode.
  EXPECT_FALSE(InOverviewSession());
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  // Cancelling shouldn't change any raster scales.
  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  // Cancel entering overview mode.
  EXPECT_TRUE(InOverviewSession());
  ToggleOverview();
  EXPECT_FALSE(InOverviewSession());

  // Cancelling shouldn't change any raster scales.
  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  // Cancel leaving overview mode.
  EXPECT_FALSE(InOverviewSession());
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  // Cancelling shouldn't change any raster scales.
  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  // Finally fully enter overview mode.
  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());
}

// Tests raster scale changes for transient windows.
TEST_P(OverviewRasterScaleTest, RasterScaleTransientChildWindows) {
  std::unique_ptr<aura::Window> window_grow_covered(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_covered(
      CreateTestWindow(kInitWindowBoundsToShrink));
  std::unique_ptr<aura::Window> window_maximized(
      CreateTestWindow(gfx::Rect(100, 100)));
  std::unique_ptr<aura::Window> window_grow_animated(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_animated(
      CreateTestWindow(kInitWindowBoundsToShrink));
  std::unique_ptr<aura::Window> window_grow_minimized(
      CreateTestWindow(kInitWindowBoundsToGrow));
  std::unique_ptr<aura::Window> window_shrink_minimized(
      CreateTestWindow(kInitWindowBoundsToShrink));

  std::unique_ptr<aura::Window> window_grow_covered_transient(
      CreateTestWindow(gfx::Rect(25, 25)));
  std::unique_ptr<aura::Window> window_shrink_covered_transient(
      CreateTestWindow(gfx::Rect(25, 25)));
  std::unique_ptr<aura::Window> window_maximized_transient(
      CreateTestWindow(gfx::Rect(25, 25)));
  std::unique_ptr<aura::Window> window_grow_animated_transient(
      CreateTestWindow(gfx::Rect(25, 25)));
  std::unique_ptr<aura::Window> window_shrink_animated_transient(
      CreateTestWindow(gfx::Rect(25, 25)));
  std::unique_ptr<aura::Window> window_grow_minimized_transient(
      CreateTestWindow(gfx::Rect(25, 25)));
  std::unique_ptr<aura::Window> window_shrink_minimized_transient(
      CreateTestWindow(gfx::Rect(25, 25)));

  wm::AddTransientChild(window_grow_covered.get(),
                        window_grow_covered_transient.get());
  wm::AddTransientChild(window_shrink_covered.get(),
                        window_shrink_covered_transient.get());
  wm::AddTransientChild(window_maximized.get(),
                        window_maximized_transient.get());
  wm::AddTransientChild(window_grow_animated.get(),
                        window_grow_animated_transient.get());
  wm::AddTransientChild(window_shrink_animated.get(),
                        window_shrink_animated_transient.get());
  wm::AddTransientChild(window_grow_minimized.get(),
                        window_grow_minimized_transient.get());
  wm::AddTransientChild(window_shrink_minimized.get(),
                        window_shrink_minimized_transient.get());

  MinimizeAndCheckWindow(window_grow_minimized.get());
  MinimizeAndCheckWindow(window_shrink_minimized.get());
  MaximizeAndCheckWindow(window_maximized.get());

  window_maximized->parent()->StackChildAtBottom(window_grow_covered.get());
  window_maximized->parent()->StackChildAtBottom(window_shrink_covered.get());
  window_maximized->parent()->StackChildAtTop(window_shrink_animated.get());
  window_maximized->parent()->StackChildAtTop(window_grow_animated.get());

  auto tracker_grow_covered =
      RasterScaleChangeTracker(window_grow_covered.get());
  auto tracker_shrink_covered =
      RasterScaleChangeTracker(window_shrink_covered.get());
  auto tracker_maximized = RasterScaleChangeTracker(window_maximized.get());
  auto tracker_grow_animated =
      RasterScaleChangeTracker(window_grow_animated.get());
  auto tracker_shrink_animated =
      RasterScaleChangeTracker(window_shrink_animated.get());
  auto tracker_grow_minimized =
      RasterScaleChangeTracker(window_grow_minimized.get());
  auto tracker_shrink_minimized =
      RasterScaleChangeTracker(window_shrink_minimized.get());

  auto tracker_grow_covered_transient =
      RasterScaleChangeTracker(window_grow_covered_transient.get());
  auto tracker_shrink_covered_transient =
      RasterScaleChangeTracker(window_shrink_covered_transient.get());
  auto tracker_maximized_transient =
      RasterScaleChangeTracker(window_maximized_transient.get());
  auto tracker_grow_animated_transient =
      RasterScaleChangeTracker(window_grow_animated_transient.get());
  auto tracker_shrink_animated_transient =
      RasterScaleChangeTracker(window_shrink_animated_transient.get());
  auto tracker_grow_minimized_transient =
      RasterScaleChangeTracker(window_grow_minimized_transient.get());
  auto tracker_shrink_minimized_transient =
      RasterScaleChangeTracker(window_shrink_minimized_transient.get());

  gfx::Rect start_bounds_grow_covered =
      GetTransformedTargetBounds(window_grow_covered.get());
  gfx::Rect start_bounds_shrink_covered =
      GetTransformedTargetBounds(window_shrink_covered.get());
  gfx::Rect start_bounds_maximized =
      GetTransformedTargetBounds(window_maximized.get());
  gfx::Rect start_bounds_grow_animated =
      GetTransformedTargetBounds(window_grow_animated.get());
  gfx::Rect start_bounds_shrink_animated =
      GetTransformedTargetBounds(window_shrink_animated.get());
  gfx::Rect start_bounds_grow_minimized =
      GetTransformedTargetBounds(window_grow_minimized.get());
  gfx::Rect start_bounds_shrink_minimized =
      GetTransformedTargetBounds(window_shrink_minimized.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(empty, tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_grow_covered =
      ExpectedRasterScale(window_grow_covered.get(), start_bounds_grow_covered,
                          /*window_grows=*/true);
  float raster_scale_shrink_covered = ExpectedRasterScale(
      window_shrink_covered.get(), start_bounds_shrink_covered,
      /*window_grows=*/false);
  float raster_scale_maximized =
      ExpectedRasterScale(window_maximized.get(), start_bounds_maximized,
                          /*window_grows=*/false);
  float raster_scale_grow_animated =
      ExpectedRasterScale(window_grow_animated.get(),
                          start_bounds_grow_animated, /*window_grows=*/true);
  float raster_scale_shrink_animated = ExpectedRasterScale(
      window_shrink_animated.get(), start_bounds_shrink_animated,
      /*window_grows=*/false);
  float raster_scale_grow_minimized =
      ExpectedRasterScale(window_grow_minimized.get(),
                          start_bounds_grow_minimized, /*window_grows=*/true);
  float raster_scale_shrink_minimized = ExpectedRasterScale(
      window_shrink_minimized.get(), start_bounds_shrink_minimized,
      /*window_grows=*/false);

  EXPECT_EQ(std::vector<float>{raster_scale_grow_covered},
            tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_covered},
            tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(std::vector<float>{raster_scale_grow_covered},
            tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_covered},
            tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(empty, tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  // Exit overview mode.
  ToggleOverview();

  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(empty, tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  // Re-enter overview mode to test adding/removing transient child windows.
  ToggleOverview();

  EXPECT_EQ(std::vector<float>{raster_scale_grow_covered},
            tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_covered},
            tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(std::vector<float>{raster_scale_grow_covered},
            tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_covered},
            tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(empty, tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  wm::RemoveTransientChild(window_grow_covered.get(),
                           window_grow_covered_transient.get());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_covered_transient.TakeRasterScaleChanges());

  wm::RemoveTransientChild(window_shrink_covered.get(),
                           window_shrink_covered_transient.get());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_covered_transient.TakeRasterScaleChanges());

  wm::RemoveTransientChild(window_maximized.get(),
                           window_maximized_transient.get());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized_transient.TakeRasterScaleChanges());

  wm::RemoveTransientChild(window_grow_animated.get(),
                           window_grow_animated_transient.get());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_animated_transient.TakeRasterScaleChanges());

  wm::RemoveTransientChild(window_shrink_animated.get(),
                           window_shrink_animated_transient.get());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_animated_transient.TakeRasterScaleChanges());

  wm::RemoveTransientChild(window_grow_minimized.get(),
                           window_grow_minimized_transient.get());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_minimized_transient.TakeRasterScaleChanges());

  wm::RemoveTransientChild(window_shrink_minimized.get(),
                           window_shrink_minimized_transient.get());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  // Add back the transient child windows and expect the raster scales to be
  // set.

  wm::AddTransientChild(window_grow_covered.get(),
                        window_grow_covered_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_covered},
            tracker_grow_covered_transient.TakeRasterScaleChanges());

  wm::AddTransientChild(window_shrink_covered.get(),
                        window_shrink_covered_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_covered},
            tracker_shrink_covered_transient.TakeRasterScaleChanges());

  wm::AddTransientChild(window_maximized.get(),
                        window_maximized_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized_transient.TakeRasterScaleChanges());

  wm::AddTransientChild(window_grow_animated.get(),
                        window_grow_animated_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_animated},
            tracker_grow_animated_transient.TakeRasterScaleChanges());

  wm::AddTransientChild(window_shrink_animated.get(),
                        window_shrink_animated_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_animated},
            tracker_shrink_animated_transient.TakeRasterScaleChanges());

  wm::AddTransientChild(window_grow_minimized.get(),
                        window_grow_minimized_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale_grow_minimized},
            tracker_grow_minimized_transient.TakeRasterScaleChanges());

  wm::AddTransientChild(window_shrink_minimized.get(),
                        window_shrink_minimized_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale_shrink_minimized},
            tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  // Test adding/removing transient windows during overview animation.

  // Exit overview mode to test adding/removing transient child windows.
  ToggleOverview();

  EXPECT_EQ(empty, tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(empty, tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_minimized_transient.TakeRasterScaleChanges());

  // Expect that transient children added during the overview mode animation
  // have their raster scale set at the end of the animation.
  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_covered.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_covered.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_animated.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_minimized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_minimized.TakeRasterScaleChanges());

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_covered_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_maximized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_shrink_animated_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_grow_minimized_transient.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_shrink_minimized_transient.TakeRasterScaleChanges());
}

// Tests that adding a window as a transient window to another window will
// update its raster scale.
TEST_P(OverviewRasterScaleTest,
       RasterScaleAddRemoveTransientChildWindowsDuringOverviewMode) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(100, 100)));

  std::unique_ptr<aura::Window> window_transient(
      CreateTestWindow(kInitWindowBoundsToShrink));

  auto tracker = RasterScaleChangeTracker(window.get());
  auto tracker_transient = RasterScaleChangeTracker(window_transient.get());

  gfx::Rect start_bounds = GetTransformedTargetBounds(window.get());
  gfx::Rect start_bounds_transient =
      GetTransformedTargetBounds(window_transient.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_transient.TakeRasterScaleChanges());

  ToggleOverview();

  auto raster_scale =
      ExpectedRasterScale(window.get(), start_bounds, /*window_grows=*/true);
  float raster_scale_transient =
      ExpectedRasterScale(window_transient.get(), start_bounds_transient,
                          /*window_grows=*/false);

  EXPECT_EQ(std::vector<float>{raster_scale}, tracker.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_transient.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_transient},
            tracker_transient.TakeRasterScaleChanges());

  // Add transient windows and expect the raster scales to be updated to the
  // larger value.
  wm::AddTransientChild(window.get(), window_transient.get());
  EXPECT_EQ(std::vector<float>{raster_scale},
            tracker_transient.TakeRasterScaleChanges());
}

// Tests that adding windows to overview mode will update existing raster
// scales.
TEST_P(OverviewRasterScaleTest,
       RasterScaleAddWindowsDuringOverviewModeByCombiningAVirtualDesk) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // First ensure there are 3 desks.
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  ASSERT_EQ(3u, controller->desks().size());

  Desk* desk1 = controller->desks()[0].get();
  Desk* desk2 = controller->desks()[1].get();
  Desk* desk3 = controller->desks()[2].get();

  // Create three windows, one on each desk. Need to use `CreateAppWindow` to
  // work with desks.
  std::unique_ptr<aura::Window> window1(CreateAppWindow(gfx::Rect(100, 100)));
  auto tracker1 = RasterScaleChangeTracker(window1.get());
  gfx::Rect start_bounds = GetTransformedTargetBounds(window1.get());
  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  controller->SendToDeskAtIndex(window1.get(), 0);
  EXPECT_TRUE(base::Contains(desk1->windows(), window1.get()));

  std::unique_ptr<aura::Window> window2(CreateAppWindow(gfx::Rect(100, 100)));
  auto tracker2 = RasterScaleChangeTracker(window2.get());
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());
  controller->SendToDeskAtIndex(window2.get(), 1);
  EXPECT_TRUE(base::Contains(desk2->windows(), window2.get()));

  std::unique_ptr<aura::Window> window3(CreateAppWindow(gfx::Rect(100, 100)));
  auto tracker3 = RasterScaleChangeTracker(window3.get());
  EXPECT_EQ(empty, tracker3.TakeRasterScaleChanges());
  controller->SendToDeskAtIndex(window3.get(), 2);
  EXPECT_TRUE(base::Contains(desk3->windows(), window3.get()));

  EXPECT_TRUE(desk1->is_active());

  // Enter overview mode
  ToggleOverview();

  auto raster_scale =
      ExpectedRasterScale(window1.get(), start_bounds, /*window_grows=*/true);
  EXPECT_EQ(std::vector<float>{raster_scale},
            tracker1.TakeRasterScaleChanges());

  // `window2` and `window3` are not visible on the overview, so expect no
  // raster scale changes.
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker3.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker3.TakeRasterScaleChanges());

  // Combine `desk3` and expect the raster scale for window3 to be updated since
  // it is moved to the active desk1.
  const auto* overview_grid =
      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
  EXPECT_EQ(1u, overview_grid->item_list().size());
  const auto* desks_bar_view = overview_grid->desks_bar_view();
  ASSERT_TRUE(desks_bar_view);
  ASSERT_EQ(3u, desks_bar_view->mini_views().size());
  auto* mini_view = desks_bar_view->mini_views()[2].get();
  EXPECT_EQ(desk3, mini_view->desk());
  if (features::IsSavedDeskUiRevampEnabled()) {
    views::MenuItemView* combine_item_view =
        DesksTestApi::OpenDeskContextMenuAndGetMenuItem(
            Shell::GetPrimaryRootWindow(), DeskBarViewBase::Type::kOverview,
            /*index=*/2, DeskActionContextMenu::CommandId::kCombineDesks);
    LeftClickOn(combine_item_view);
  } else {
    CombineDesksViaMiniView(mini_view, GetEventGenerator());
  }

  EXPECT_TRUE(desk1->is_active());
  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale},
            tracker3.TakeRasterScaleChanges());

  // Now combine the active desk (`desk1`), and expect only `window2` to be
  // updated.
  EXPECT_EQ(2u, overview_grid->item_list().size());
  mini_view = desks_bar_view->mini_views()[0];
  EXPECT_EQ(desk1, mini_view->desk());
  if (features::IsSavedDeskUiRevampEnabled()) {
    views::MenuItemView* combine_item_view =
        DesksTestApi::OpenDeskContextMenuAndGetMenuItem(
            Shell::GetPrimaryRootWindow(), DeskBarViewBase::Type::kOverview,
            /*index=*/0, DeskActionContextMenu::CommandId::kCombineDesks);
    LeftClickOn(combine_item_view);
  } else {
    CombineDesksViaMiniView(mini_view, GetEventGenerator());
  }

  EXPECT_TRUE(desk2->is_active());
  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale},
            tracker2.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker3.TakeRasterScaleChanges());
}

// Tests that moving windows from overview mode to a different virtual desk
// works.
TEST_P(OverviewRasterScaleTest,
       RasterScaleMoveWindowToVirtualDeskDuringOverviewMode) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // First ensure there are 3 desks.
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  EXPECT_EQ(2u, controller->desks().size());

  Desk* desk1 = controller->desks()[0].get();
  Desk* desk2 = controller->desks()[1].get();

  // Create two windows on the first desk.
  std::unique_ptr<aura::Window> window1(CreateAppWindow(gfx::Rect(100, 100)));
  auto tracker1 = RasterScaleChangeTracker(window1.get());
  gfx::Rect start_bounds = GetTransformedTargetBounds(window1.get());
  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  controller->SendToDeskAtIndex(window1.get(), 0);
  EXPECT_TRUE(base::Contains(desk1->windows(), window1.get()));

  std::unique_ptr<aura::Window> window2(CreateAppWindow(gfx::Rect(100, 100)));
  auto tracker2 = RasterScaleChangeTracker(window2.get());
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());
  controller->SendToDeskAtIndex(window2.get(), 0);
  EXPECT_TRUE(base::Contains(desk1->windows(), window2.get()));

  EXPECT_TRUE(desk1->is_active());

  // Enter overview mode
  ToggleOverview();

  auto raster_scale =
      ExpectedRasterScale(window1.get(), start_bounds, /*window_grows=*/true);
  EXPECT_EQ(std::vector<float>{raster_scale},
            tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale},
            tracker2.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());

  // Move `window2` to `desk2` and expect it to go back to 1.0 raster scale.
  EXPECT_TRUE(controller->MoveWindowFromActiveDeskTo(
      window2.get(), desk2, window2->GetRootWindow(),
      DesksMoveWindowFromActiveDeskSource::kDragAndDrop));

  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0f}, tracker2.TakeRasterScaleChanges());

  // Exit overview mode
  ToggleOverview();
  EXPECT_EQ(empty, tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());

  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{1.0f}, tracker1.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker2.TakeRasterScaleChanges());
}

// Tests raster scale changes work in tablet mode.
// TODO(crbug.com/40949385): Fix flaky test.
TEST_P(OverviewRasterScaleTest, DISABLED_RasterScaleTabletMode) {
  EnterTabletMode();

  std::unique_ptr<aura::Window> window_maximized(
      CreateTestWindow(gfx::Rect(600, 600)));
  std::unique_ptr<aura::Window> window_minimized(
      CreateTestWindow(gfx::Rect(600, 600)));

  MinimizeAndCheckWindow(window_minimized.get());
  MaximizeAndCheckWindow(window_maximized.get());

  auto tracker_maximized = RasterScaleChangeTracker(window_maximized.get());
  auto tracker_minimized = RasterScaleChangeTracker(window_minimized.get());

  gfx::Rect start_bounds_maximized =
      GetTransformedTargetBounds(window_maximized.get());
  gfx::Rect start_bounds_minimized =
      GetTransformedTargetBounds(window_minimized.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_maximized =
      ExpectedRasterScale(window_maximized.get(), start_bounds_maximized,
                          /*window_grows=*/false);
  float raster_scale_minimized =
      ExpectedRasterScale(window_minimized.get(), start_bounds_minimized,
                          /*window_grows=*/false);

  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_minimized},
            tracker_minimized.TakeRasterScaleChanges());

  // Cancel entering overview mode.
  ToggleOverview();

  // Animation will start to reverse, no reason to change the raster scale here.
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{1.0},
            tracker_minimized.TakeRasterScaleChanges());

  // Enter overview mode so we can test cancelling exit.
  ToggleOverview();

  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_minimized},
            tracker_minimized.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  // In overview mode. Start exiting and then cancel.
  ToggleOverview();

  EXPECT_EQ(std::vector<float>{1.0},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  // Cancel leaving overview mode.
  EXPECT_FALSE(InOverviewSession());
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  // Cancelling shouldn't change any raster scales.
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  // Cancel entering overview mode.
  EXPECT_TRUE(InOverviewSession());
  ToggleOverview();
  EXPECT_FALSE(InOverviewSession());

  // Cancelling shouldn't change any raster scales.
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  // Cancel leaving overview mode.
  EXPECT_FALSE(InOverviewSession());
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());

  // Cancelling shouldn't change any raster scales.
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  // Finally fully enter overview mode.
  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());
}

// Tests raster scale changes work during screen rotations.
TEST_P(OverviewRasterScaleTest, RasterScaleScreenRotation) {
  UpdateDisplay("1600x1200");
  display::test::ScopedSetInternalDisplayId set_internal(
      Shell::Get()->display_manager(),
      display::Screen::GetScreen()->GetPrimaryDisplay().id());
  ScreenOrientationControllerTestApi test_api(
      Shell::Get()->screen_orientation_controller());

  std::unique_ptr<aura::Window> window_maximized(
      CreateTestWindow(gfx::Rect(600, 600)));
  std::unique_ptr<aura::Window> window_minimized(
      CreateTestWindow(gfx::Rect(600, 600)));

  MinimizeAndCheckWindow(window_minimized.get());
  MaximizeAndCheckWindow(window_maximized.get());

  auto tracker_maximized = RasterScaleChangeTracker(window_maximized.get());
  auto tracker_minimized = RasterScaleChangeTracker(window_minimized.get());

  gfx::Rect start_bounds_maximized =
      GetTransformedTargetBounds(window_maximized.get());
  gfx::Rect start_bounds_minimized =
      GetTransformedTargetBounds(window_minimized.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_maximized =
      ExpectedRasterScale(window_maximized.get(), start_bounds_maximized,
                          /*window_grows=*/false);
  float raster_scale_minimized =
      ExpectedRasterScale(window_minimized.get(), start_bounds_minimized,
                          /*window_grows=*/false);

  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_minimized},
            tracker_minimized.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  // Rotate the screen 180 degrees and expect no raster scale changes.
  test_api.SetDisplayRotation(display::Display::ROTATE_0,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  test_api.SetDisplayRotation(display::Display::ROTATE_180,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());
}

// Tests raster scale changes work during screen rotations in tablet mode.
TEST_P(OverviewRasterScaleTest, RasterScaleScreenRotationTabletMode) {
  UpdateDisplay("1600x1200");
  EnterTabletMode();

  display::test::ScopedSetInternalDisplayId set_internal(
      Shell::Get()->display_manager(),
      display::Screen::GetScreen()->GetPrimaryDisplay().id());
  ScreenOrientationControllerTestApi test_api(
      Shell::Get()->screen_orientation_controller());

  std::unique_ptr<aura::Window> window_maximized(
      CreateTestWindow(gfx::Rect(600, 600)));
  std::unique_ptr<aura::Window> window_minimized(
      CreateTestWindow(gfx::Rect(600, 600)));

  MinimizeAndCheckWindow(window_minimized.get());
  MaximizeAndCheckWindow(window_maximized.get());

  auto tracker_maximized = RasterScaleChangeTracker(window_maximized.get());
  auto tracker_minimized = RasterScaleChangeTracker(window_minimized.get());

  gfx::Rect start_bounds_maximized =
      GetTransformedTargetBounds(window_maximized.get());
  gfx::Rect start_bounds_minimized =
      GetTransformedTargetBounds(window_minimized.get());
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const std::vector<float> empty;
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  ToggleOverview();

  float raster_scale_maximized =
      ExpectedRasterScale(window_maximized.get(), start_bounds_maximized,
                          /*window_grows=*/false);
  float raster_scale_minimized =
      ExpectedRasterScale(window_minimized.get(), start_bounds_minimized,
                          /*window_grows=*/false);

  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{raster_scale_minimized},
            tracker_minimized.TakeRasterScaleChanges());

  WaitForOverviewEnterAnimation();
  // Wait for the occlusion tracker to be unpaused after overview enter.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(std::vector<float>{raster_scale_maximized},
            tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  // Rotate the screen 180 degrees and expect no raster scale changes.
  test_api.SetDisplayRotation(display::Display::ROTATE_0,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());

  test_api.SetDisplayRotation(display::Display::ROTATE_180,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(empty, tracker_maximized.TakeRasterScaleChanges());
  EXPECT_EQ(empty, tracker_minimized.TakeRasterScaleChanges());
}

INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
                         OverviewRasterScaleTest,
                         testing::Combine(testing::Bool(), testing::Bool()),
                         OverviewSessionTestParamsToString);

class FloatOverviewSessionTest : public OverviewTestBase {
 public:
  FloatOverviewSessionTest() = default;
  FloatOverviewSessionTest(const FloatOverviewSessionTest&) = delete;
  FloatOverviewSessionTest& operator=(const FloatOverviewSessionTest&) = delete;
  ~FloatOverviewSessionTest() override = default;

  // Checks if the float container is in its regular position. Returns false if
  // it is not true on any of the root windows.
  bool IsFloatContainerNormalStacked() const {
    for (aura::Window* root : Shell::GetAllRootWindows()) {
      if (features::IsForestFeatureEnabled()) {
        // The float container should be the top-most child of the
        // `ShutdownScreenshotContainer` when the feature `ForestFeature` is
        // enabled.
        auto* shutdown_screenshot_container =
            root->GetChildById(kShellWindowId_ShutdownScreenshotContainer);
        EXPECT_EQ(root->GetChildById(kShellWindowId_FloatContainer),
                  shutdown_screenshot_container->children().back());
      } else {
        // The float container should above the always on top container and
        // below the app list container when the `ForestFeature` is not enabled.
        if (!window_util::IsStackedBelow(
                root->GetChildById(kShellWindowId_AlwaysOnTopContainer),
                root->GetChildById(kShellWindowId_FloatContainer))) {
          return false;
        }
        if (!window_util::IsStackedBelow(
                root->GetChildById(kShellWindowId_FloatContainer),
                root->GetChildById(kShellWindowId_AppListContainer))) {
          return false;
        }
      }
    }

    return true;
  }

  bool IsFloatContainerBelowActiveDesk() const {
    for (aura::Window* root : Shell::GetAllRootWindows()) {
      if (!window_util::IsStackedBelow(
              root->GetChildById(kShellWindowId_FloatContainer),
              root->GetChildById(kShellWindowId_DeskContainerA))) {
        return false;
      }
    }

    return true;
  }
};

// Tests that the float container is stacked properly when entering and exiting
// overview mode.
TEST_F(FloatOverviewSessionTest, FloatContainerStacking) {
  UpdateDisplay("800x600,800x600");

  // We need at least one window for an overview enter animation.
  auto window = CreateAppWindow();

  ui::ScopedAnimationDurationScaleMode duration_scale(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  EXPECT_TRUE(IsFloatContainerNormalStacked());

  // Enter overview. The float container remains above the active desk until
  // after the overview enter animation is over.
  ToggleOverview();
  EXPECT_FALSE(IsFloatContainerBelowActiveDesk());
  WaitForOverviewEnterAnimation();
  EXPECT_TRUE(IsFloatContainerBelowActiveDesk());

  // Exit overview. The float container is stacked in its normal position prior
  // to the exit animation starting.
  ToggleOverview();
  EXPECT_FALSE(IsFloatContainerBelowActiveDesk());
  WaitForOverviewExitAnimation();
  // Wait for the occlusion tracker to be unpaused after overview exit.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(IsFloatContainerNormalStacked());

  // Start overview but exit before the animation is complete. Verify the float
  // container is stacked in its normal position.
  ToggleOverview();
  ToggleOverview();
  EXPECT_TRUE(IsFloatContainerNormalStacked());
}

// Tests that when we drag in overview, and there is a floated window, the
// float container gets restacked so it will appear under the dragged window.
// See b/252504134 for more details.
TEST_F(FloatOverviewSessionTest, DraggingWithFloatedWindow) {
  UpdateDisplay("800x600,800x600");

  // Create one normal and one floated window.
  auto normal_window = CreateAppWindow();
  auto floated_window = CreateAppWindow();
  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  ASSERT_TRUE(WindowState::Get(floated_window.get())->IsFloated());

  ToggleOverview();
  ASSERT_TRUE(IsFloatContainerBelowActiveDesk());

  auto* normal_item = GetOverviewItemForWindow(normal_window.get());
  auto* floated_item = GetOverviewItemForWindow(floated_window.get());

  // Start dragging the floated window. Check that the float container gets
  // stacked above the desk container after dragging starts.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(floated_item->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  generator->MoveMouseBy(10, 10);
  EXPECT_TRUE(IsFloatContainerNormalStacked());

  generator->ReleaseLeftButton();

  // Dragging the normal window does not cause restacking as it is already on
  // top of other windows like it should be.
  ASSERT_TRUE(IsFloatContainerBelowActiveDesk());
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(normal_item->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  generator->MoveMouseBy(10, 10);
  EXPECT_TRUE(IsFloatContainerBelowActiveDesk());

  generator->ReleaseLeftButton();
  ASSERT_TRUE(InOverviewSession());
  EXPECT_TRUE(IsFloatContainerBelowActiveDesk());

  // Tests that the stacking order is correct if we start dragging a normal
  // overview item, and then exit overview.
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(normal_item->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  ToggleOverview();
  // `OverviewWindowDragController` gets deleted using `DeleteSoon()`.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsFloatContainerNormalStacked());
}

// Tests that clicking the normal window to activate it does not result in a
// crash. Regression test for b/258818000.
TEST_F(FloatOverviewSessionTest, ClickingWithFloatedWindow) {
  // Create one normal and one floated window.
  auto normal_window = CreateAppWindow();
  auto floated_window = CreateAppWindow();
  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  ASSERT_TRUE(WindowState::Get(floated_window.get())->IsFloated());

  ToggleOverview();
  auto* normal_item = GetOverviewItemForWindow(normal_window.get());
  GetEventGenerator()->set_current_screen_location(
      gfx::ToRoundedPoint(normal_item->target_bounds().CenterPoint()));
  GetEventGenerator()->ClickLeftButton();
}

// Tests that dragging a normal window while there is a floated window to a new
// desk does not result in a crash. Regression test for http://b/261757970.
TEST_F(FloatOverviewSessionTest, DraggingToNewDeskWithFloatedWindow) {
  // Create one normal and one floated window.
  auto normal_window = CreateAppWindow();
  auto floated_window = CreateAppWindow();
  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  ASSERT_TRUE(WindowState::Get(floated_window.get())->IsFloated());

  // Enter overview and start dragging on the normal window.
  ToggleOverview();
  auto* normal_item = GetOverviewItemForWindow(normal_window.get());
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(normal_item->target_bounds().CenterPoint()));
  generator->PressLeftButton();

  // Drag the normal window to the new desk button; this will create a new desk
  // and drop the normal window in it.
  OverviewGrid* overview_grid =
      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
  const auto* desks_bar_view = overview_grid->desks_bar_view();
  ASSERT_TRUE(desks_bar_view);

  const DeskIconButton* new_desk_button = desks_bar_view->new_desk_button();
  ASSERT_TRUE(new_desk_button);
  ASSERT_TRUE(new_desk_button->GetVisible());
  generator->DragMouseTo(new_desk_button->GetBoundsInScreen().CenterPoint());

  // Check that a new desk has been created, and there should be no crash when
  // dropping the window.
  generator->ReleaseLeftButton();
  auto* controller = DesksController::Get();
  EXPECT_EQ(2u, controller->desks().size());
  EXPECT_TRUE(base::Contains(controller->GetDeskAtIndex(1)->windows(),
                             normal_window.get()));
}

// Tests that the overview item associated with the floated window appears
// underneath the about to be dragged window after long pressing.
TEST_F(FloatOverviewSessionTest, LongPressingWithFloatedWindow) {
  // Shorten the long press times so we don't have to delay as long.
  ui::GestureConfiguration* gesture_config =
      ui::GestureConfiguration::GetInstance();
  gesture_config->set_long_press_time_in_ms(1);
  gesture_config->set_short_press_time(base::Milliseconds(1));
  gesture_config->set_show_press_delay_in_ms(1);

  // Create one normal and one floated window.
  auto normal_window = CreateAppWindow();
  auto floated_window = CreateAppWindow();
  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  ASSERT_TRUE(WindowState::Get(floated_window.get())->IsFloated());

  ToggleOverview();
  ASSERT_TRUE(IsFloatContainerBelowActiveDesk());

  // Simulate a long press on the overview item of the floated window.
  auto* float_item = GetOverviewItemForWindow(floated_window.get());
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(float_item->target_bounds().CenterPoint()));
  generator->PressTouch();
  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2));
  run_loop.Run();

  // After long pressing, the float container should be stacked above the desk
  // container so that the overview item of the float window appears above
  // during the drag.
  EXPECT_TRUE(IsFloatContainerNormalStacked());

  // Test that on release, the float container is stacked below the desk
  // container again.
  generator->ReleaseTouch();
  EXPECT_TRUE(IsFloatContainerBelowActiveDesk());
}

class TabletModeOverviewSessionTest : public OverviewTestBase {
 public:
  TabletModeOverviewSessionTest() = default;
  TabletModeOverviewSessionTest(const TabletModeOverviewSessionTest&) = delete;
  TabletModeOverviewSessionTest& operator=(
      const TabletModeOverviewSessionTest&) = delete;
  ~TabletModeOverviewSessionTest() override = default;

  // OverviewTestBase:
  void SetUp() override {
    OverviewTestBase::SetUp();
    EnterTabletMode();
  }

  SplitViewController* split_view_controller() {
    return SplitViewController::Get(Shell::GetPrimaryRootWindow());
  }

 protected:
  void GenerateScrollSequence(const gfx::Point& start, const gfx::Point& end) {
    GetEventGenerator()->GestureScrollSequence(start, end,
                                               base::Milliseconds(100), 1000);
  }

  void DispatchLongPress(OverviewItemBase* item) {
    const gfx::Point point =
        gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
    ui::GestureEvent long_press(
        point.x(), point.y(), 0, base::TimeTicks::Now(),
        ui::GestureEventDetails(ui::EventType::kGestureLongPress));
    GetEventGenerator()->Dispatch(&long_press);
  }

  // Creates `n` test windows. They are created in reverse order, so that the
  // first window in the vector is the MRU window.
  std::vector<std::unique_ptr<aura::Window>> CreateAppWindows(int n) {
    std::vector<std::unique_ptr<aura::Window>> windows(n);
    for (int i = n - 1; i >= 0; --i) {
      windows[i] = CreateTestWindow();
    }
    return windows;
  }
};

// Tests that windows are in proper positions in the new overview layout.
TEST_F(TabletModeOverviewSessionTest, CheckNewLayoutWindowPositions) {
  auto windows = CreateAppWindows(6);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* item1 = GetOverviewItemForWindow(windows[0].get());
  auto* item2 = GetOverviewItemForWindow(windows[1].get());
  auto* item3 = GetOverviewItemForWindow(windows[2].get());
  auto* item4 = GetOverviewItemForWindow(windows[3].get());

  const gfx::RectF item1_bounds = item1->target_bounds();
  const gfx::RectF item2_bounds = item2->target_bounds();
  const gfx::RectF item3_bounds = item3->target_bounds();
  const gfx::RectF item4_bounds = item4->target_bounds();

  // |window1| should be in the top left position. |window2| should be directly
  // below |window1|, thus sharing the same x-value but not the same y-value.
  EXPECT_EQ(item1_bounds.x(), item2_bounds.x());
  EXPECT_LT(item1_bounds.y(), item2_bounds.y());
  // |window3| should be directly right of |window1|, thus sharing the same
  // y-value, but not the same x-value.
  EXPECT_LT(item1_bounds.x(), item3_bounds.x());
  EXPECT_EQ(item1_bounds.y(), item3_bounds.y());
  // |window4| should be directly right of |window2| and directly below
  // |window3|.
  EXPECT_LT(item2_bounds.x(), item4_bounds.x());
  EXPECT_EQ(item2_bounds.y(), item4_bounds.y());
  EXPECT_EQ(item3_bounds.x(), item4_bounds.x());
  EXPECT_LT(item3_bounds.y(), item4_bounds.y());
}

// Tests that with the tablet mode layout, some of the windows are offscreen.
TEST_F(TabletModeOverviewSessionTest, CheckOffscreenWindows) {
  auto windows = CreateAppWindows(10);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* item0 = GetOverviewItemForWindow(windows[0].get());
  auto* item1 = GetOverviewItemForWindow(windows[1].get());
  auto* item8 = GetOverviewItemForWindow(windows[8].get());
  auto* item9 = GetOverviewItemForWindow(windows[9].get());

  const gfx::RectF screen_bounds(GetGridBounds());
  const gfx::RectF item0_bounds = item0->target_bounds();
  const gfx::RectF item1_bounds = item1->target_bounds();
  const gfx::RectF item8_bounds = item8->target_bounds();
  const gfx::RectF item9_bounds = item9->target_bounds();

  // |item6| should be in the same row of windows as |item0|, but offscreen
  // (one screen length away).
  EXPECT_FALSE(screen_bounds.Contains(item8_bounds));
  EXPECT_EQ(item0_bounds.y(), item8_bounds.y());
  // |item7| should be in the same row of windows as |item1|, but offscreen
  // and below |item6|.
  EXPECT_FALSE(screen_bounds.Contains(item9_bounds));
  EXPECT_EQ(item1_bounds.y(), item9_bounds.y());
  EXPECT_LT(item8_bounds.y(), item9_bounds.y());
}

// Tests to see if windows are not shifted if all already available windows
// fit on screen.
TEST_F(TabletModeOverviewSessionTest, CheckNoOverviewItemShift) {
  auto windows = CreateAppWindows(4);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* item0 = GetOverviewItemForWindow(windows[0].get());
  const gfx::RectF before_shift_bounds = item0->target_bounds();

  GenerateScrollSequence(gfx::Point(100, 60), gfx::Point(0, 50));
  EXPECT_EQ(before_shift_bounds, item0->target_bounds());
}

// Tests to see if windows are shifted if at least one window is
// partially/completely positioned offscreen.
TEST_F(TabletModeOverviewSessionTest, CheckOverviewItemShift) {
  auto windows = CreateAppWindows(9);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* item0 = GetOverviewItemForWindow(windows[0].get());
  const gfx::RectF before_shift_bounds = item0->target_bounds();

  GenerateScrollSequence(gfx::Point(100, 60), gfx::Point(0, 50));
  EXPECT_LT(item0->target_bounds(), before_shift_bounds);
}

// Tests to see if windows remain in bounds after scrolling extremely far.
TEST_F(TabletModeOverviewSessionTest, CheckOverviewItemScrollingBounds) {
  auto windows = CreateAppWindows(8);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  // Scroll an extreme amount to see if windows on the far left are still in
  // bounds. First, align the left-most window (|windows[0]|) to the left-hand
  // bound and store the item's location. Then, scroll a far amount and check to
  // see if the item moved at all.
  auto* leftmost_window = GetOverviewItemForWindow(windows[0].get());

  GenerateScrollSequence(
      gfx::Point(BackGestureEventHandler::kStartGoingBackLeftEdgeInset + 5, 50),
      gfx::Point(5000, 50));
  const gfx::RectF left_bounds = leftmost_window->target_bounds();
  GenerateScrollSequence(
      gfx::Point(BackGestureEventHandler::kStartGoingBackLeftEdgeInset + 5, 50),
      gfx::Point(5000, 50));
  EXPECT_EQ(left_bounds, leftmost_window->target_bounds());

  // Scroll an extreme amount to see if windows on the far right are still in
  // bounds. First, align the right-most window (|windows[7]|) to the right-hand
  // bound and store the item's location. Then, scroll a far amount and check to
  // see if the item moved at all.
  auto* rightmost_window = GetOverviewItemForWindow(windows[7].get());
  GenerateScrollSequence(gfx::Point(5000, 50), gfx::Point(0, 50));
  const gfx::RectF right_bounds = rightmost_window->target_bounds();
  GenerateScrollSequence(gfx::Point(5000, 50), gfx::Point(0, 50));
  EXPECT_EQ(right_bounds, rightmost_window->target_bounds());
}

// Tests that destroying a window does not cause a crash while scrolling the
// overview grid. Regression test for https://crbug.com/1200605.
TEST_F(TabletModeOverviewSessionTest, WindowDestroyWhileScrolling) {
  auto windows = CreateAppWindows(8);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  // Start a scroll sequence.
  int x = 500;
  const int y = 200;
  base::TimeTicks timestamp = ui::EventTimeForNow();
  auto* event_generator = GetEventGenerator();
  ui::TouchEvent press(ui::EventType::kTouchPressed, gfx::Point(x, y),
                       timestamp, ui::PointerDetails());
  event_generator->Dispatch(&press);

  // Scroll a bit to the left, so the overview items that are offscreen on the
  // right start to become visible.
  const base::TimeDelta step_delay = base::Milliseconds(5);
  for (int i = 0; i < 10; ++i) {
    timestamp += step_delay;
    ui::TouchEvent move(ui::EventType::kTouchMoved, gfx::Point(x, y), timestamp,
                        ui::PointerDetails());
    event_generator->Dispatch(&move);
    x -= 5;
  }

  // Delete one of the windows.
  std::erase(windows, windows[2]);

  // Continue scrolling and then end the scroll. There should be no crash.
  for (int i = 0; i < 10; ++i) {
    timestamp += step_delay;
    ui::TouchEvent move(ui::EventType::kTouchMoved, gfx::Point(x, y), timestamp,
                        ui::PointerDetails());
    event_generator->Dispatch(&move);
    x -= 5;
  }

  ui::TouchEvent release(ui::EventType::kTouchReleased, gfx::Point(x, y),
                         timestamp, ui::PointerDetails());
  event_generator->Dispatch(&release);
}

// Tests that removing a desk does not cause a crash while scrolling the
// overview grid. Regression test for https://crbug.com/1455360.
TEST_F(TabletModeOverviewSessionTest, DeskRemovalWhileScrolling) {
  // The crash happened when closing a desk (which would add its app windows as
  // items in overview) midway through a scroll. Create two desks with windows;
  // the first desk has enough windows so that overview is scrollable.
  auto desk1_windows = CreateAppWindows(15);

  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  ActivateDesk(controller->GetDeskAtIndex(1));
  auto desk2_windows = CreateAppWindows(2);

  // Activate the desk with 15 windows. There may be more than the windows we
  // created (i.e. backdrop, nudges), so we assert greater than.
  ActivateDesk(controller->GetDeskAtIndex(0));
  ASSERT_GT(controller->GetDeskAtIndex(0)->windows().size(), 15u);
  ASSERT_GT(controller->GetDeskAtIndex(1)->windows().size(), 2u);

  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  // Start scrolling the overview grid.
  GetEventGenerator()->PressTouch(gfx::Point(400, 300));
  GetEventGenerator()->MoveTouchBy(-50, 0);

  // Remove the desk and continue scrolling. There should be no crash.
  RemoveDesk(controller->GetDeskAtIndex(1), DeskCloseType::kCombineDesks);
  GetEventGenerator()->MoveTouchBy(-50, 0);
}

// Tests the windows are stacked correctly when entering or exiting splitview
// while in tablet mode.
TEST_F(TabletModeOverviewSessionTest, StackingOrderSplitViewWindow) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateUnsnappableWindow();
  std::unique_ptr<aura::Window> window3 = CreateTestWindow();

  // Snap `window1` to the left and `window3` to the right. Activate `window3`
  // so that it is stacked above `window1`.
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller()->SnapWindow(window3.get(), SnapPosition::kSecondary);
  wm::ActivateWindow(window3.get());
  ASSERT_TRUE(window_util::IsStackedBelow(window1.get(), window3.get()));

  // Test that on entering overview, `window3` is stacked below `window1`, so
  // that when we scroll the grid, it will be seen under `window1`.
  ToggleOverview();
  ASSERT_FALSE(GetOverviewItemForWindow(window1.get()));
  ASSERT_TRUE(GetOverviewItemForWindow(window2.get()));
  ASSERT_TRUE(GetOverviewItemForWindow(window3.get()));
  EXPECT_TRUE(window_util::IsStackedBelow(window3.get(), window1.get()));

  // Test that `window2` has a cannot snap widget indicating that it cannot be
  // snapped, and that both `window2` and the widget are lower z-order than
  // `window1`.
  views::Widget* cannot_snap_widget =
      GetCannotSnapWidget(GetOverviewItemForWindow(window2.get()));
  ASSERT_TRUE(cannot_snap_widget);
  aura::Window* cannot_snap_window = cannot_snap_widget->GetNativeWindow();
  ASSERT_EQ(window1->parent(), cannot_snap_window->parent());
  EXPECT_TRUE(window_util::IsStackedBelow(window2.get(), window1.get()));
  EXPECT_TRUE(window_util::IsStackedBelow(cannot_snap_window, window1.get()));

  // Test that on exiting overview, the relative stacking order between
  // `window3` and `window1` remains unchanged.
  ToggleOverview();
  EXPECT_TRUE(window_util::IsStackedBelow(window1.get(), window3.get()));
}

// Tests the windows are remain stacked underneath the split view window after
// dragging or long pressing.
TEST_F(TabletModeOverviewSessionTest, StackingOrderAfterGestureEvent) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();

  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);

  // Tests that if we long press, but cancel the event, the window stays stacked
  // under the snapped window.
  auto* item = GetOverviewItemForWindow(window2.get());
  const gfx::PointF item_center = item->target_bounds().CenterPoint();
  DispatchLongPress(item);
  ui::GestureEvent gesture_end(
      item_center.x(), item_center.y(), 0, ui::EventTimeForNow(),
      ui::GestureEventDetails(ui::EventType::kGestureEnd));
  item->HandleGestureEvent(&gesture_end, item);
  EXPECT_TRUE(window_util::IsStackedBelow(window2.get(), window1.get()));

  // Tests that if we drag the window around, then release, the window also
  // stays stacked under the snapped window.
  ASSERT_TRUE(InOverviewSession());
  const gfx::Vector2dF delta(15.f, 15.f);
  DispatchLongPress(item);
  GetOverviewSession()->Drag(item, item_center + delta);
  GetOverviewSession()->CompleteDrag(item, item_center + delta);
  EXPECT_TRUE(window_util::IsStackedBelow(window2.get(), window1.get()));
}

// Test that scrolling occurs if started on top of a window using the window's
// center-point as a start.
TEST_F(TabletModeOverviewSessionTest, HorizontalScrollingOnOverviewItem) {
  auto windows = CreateAppWindows(9);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* leftmost_window = GetOverviewItemForWindow(windows[0].get());
  const gfx::Point topleft_window_center =
      gfx::ToRoundedPoint(leftmost_window->target_bounds().CenterPoint());
  const gfx::RectF left_bounds = leftmost_window->target_bounds();

  GenerateScrollSequence(topleft_window_center, gfx::Point(-500, 50));
  EXPECT_LT(leftmost_window->target_bounds(), left_bounds);
}

// Tests that dragging a fullscreened window to snap in overview does not result
// in a u-a-f. Regression test for crbug.com/1330042.
TEST_F(TabletModeOverviewSessionTest, SnappingFullscreenWindow) {
  UpdateDisplay("800x600");

  auto window = CreateAppWindow(gfx::Rect(300, 300));

  const WMEvent fullscreen_event(WM_EVENT_FULLSCREEN);
  WindowState::Get(window.get())->OnWMEvent(&fullscreen_event);
  EXPECT_TRUE(WindowState::Get(window.get())->IsFullscreen());

  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* item = GetOverviewItemForWindow(window.get());
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(item->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  generator->MoveMouseTo(gfx::Point(10, 300));
  generator->ReleaseLeftButton();

  EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
}

class ContinuousOverviewAnimationTest
    : public OverviewTestBase,
      public testing::WithParamInterface<bool> {
 public:
  ContinuousOverviewAnimationTest() = default;
  ContinuousOverviewAnimationTest(const ContinuousOverviewAnimationTest&) =
      delete;
  ContinuousOverviewAnimationTest& operator=(
      const ContinuousOverviewAnimationTest&) = delete;
  ~ContinuousOverviewAnimationTest() override = default;

  // OverviewTestBase:
  void SetUp() override {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kContinuousOverviewScrollAnimation,
                              features::kDeskButton,
                              features::kDeskBarWindowOcclusionOptimization},
        /*disabled_features=*/{});
    OverviewTestBase::SetUp();

    // TODO(zxdan): try to get and set the reverse scrolling with input device
    // settings controller. Toggle natural scrolling. Behavior should always
    // stay the same.
    PrefService* pref_service =
        Shell::Get()->session_controller()->GetActivePrefService();
    const bool enabled = GetParam();
    pref_service->SetBoolean(prefs::kTouchpadEnabled, true);
    pref_service->SetBoolean(prefs::kNaturalScroll, enabled);
  }

  // If `complete_scroll` is false, end the scroll with the fingers still on the
  // trackpad.
  void ThreeFingerScroll(float x_offset,
                         float y_offset,
                         bool complete_scroll,
                         const gfx::Point& start = gfx::Point()) {
    // When natural (reverse) scroll is ON, the horizontal offset stays same
    // while the vertical offset is flipped.
    const bool is_reverse_on = GetParam();
    GetEventGenerator()->ScrollSequence(
        start, base::Milliseconds(5), x_offset,
        is_reverse_on ? -y_offset : y_offset,
        /*steps=*/100, /*num_fingers=*/3,
        /*end_state=*/
        complete_scroll
            ? ui::test::EventGenerator::ScrollSequenceType::UpToFling
            : ui::test::EventGenerator::ScrollSequenceType::ScrollOnly);
  }

  void SetShowDeskButton(bool visible) {
    SetShowDeskButtonInShelfPref(
        Shell::Get()->session_controller()->GetActivePrefService(), visible);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Verifies that 3 finger scroll while hovering the desk button does not open
// the app list.
TEST_P(ContinuousOverviewAnimationTest, ScrollOnDeskButtonDoesNotOpenAppList) {
  ShelfViewTestAPI test_api(GetPrimaryShelf()->GetShelfViewForTesting());

  SetShowDeskButton(true);
  // The button should be visible.
  EXPECT_TRUE(test_api.shelf_view()
                  ->shelf_widget()
                  ->desk_button_widget()
                  ->GetLayer()
                  ->GetTargetVisibility());

  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());

  // Perform a very long swipe up gesture from the center of the desk button.
  const float long_scroll = WmGestureHandler::kVerticalThresholdDp + 200.f;
  ThreeFingerScroll(0, long_scroll, /*complete_scroll=*/true,
                    test_api.shelf_view()
                        ->shelf_widget()
                        ->desk_button_widget()
                        ->GetLayer()
                        ->bounds()
                        .CenterPoint());
  // We should be in overview mode.
  ASSERT_TRUE(InOverviewSession());
  // Desk button widget should be invisible in overview mode.
  EXPECT_FALSE(test_api.shelf_view()
                   ->shelf_widget()
                   ->desk_button_widget()
                   ->GetLayer()
                   ->GetTargetVisibility());
  // 3 finger scroll from the desk button widget should not show the app list.
  GetAppListTestHelper()->CheckVisibility(false);
}

// Tests that continuous scrolls slowly shrink active windows and increase the
// opacity of minimized windows, regardless of the state of `NaturalScroll`.
TEST_P(ContinuousOverviewAnimationTest, WindowSizesAndOpacities) {
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());
  std::unique_ptr<aura::Window> minimized_window(CreateTestWindow());
  WindowState::Get(minimized_window.get())->Minimize();

  // Get the original positions.
  const gfx::Rect original_bounds1 = window1->bounds();
  const gfx::Rect original_bounds2 = window2->bounds();
  const gfx::Rect original_bounds3 = window3->bounds();

  // Get the final positions by toggling overview mode regularly.
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());

  const gfx::Rect final_bounds1 = gfx::ToEnclosedRect(item1->target_bounds());
  const gfx::Rect final_bounds2 = gfx::ToEnclosedRect(item2->target_bounds());
  const gfx::Rect final_bounds3 = gfx::ToEnclosedRect(item3->target_bounds());

  ToggleOverview();
  ASSERT_FALSE(InOverviewSession());

  // Swipe up a little bit and keep the fingers rested on the trackpad so that
  // the window placements are paused. Technically, we are in an overview
  // session, but the windows have not been placed in their final positions yet
  // due to the scroll still being in progress.
  const float short_scroll = 50.f;
  ThreeFingerScroll(0, short_scroll, /*complete_scroll=*/false);
  ASSERT_TRUE(InOverviewSession());

  // Get the current window positions and opacities.
  int top_inset = window1.get()->GetProperty(aura::client::kTopViewInset);
  gfx::RectF curr_bounds1 =
      window_util::GetTransformedBounds(window1.get(), top_inset);
  gfx::RectF curr_bounds2 =
      window_util::GetTransformedBounds(window2.get(), top_inset);
  gfx::RectF curr_bounds3 =
      window_util::GetTransformedBounds(window3.get(), top_inset);

  // Each active window should be smaller than their original state, but larger
  // than their final overview mode state.
  EXPECT_LT(curr_bounds1.width(), original_bounds1.width());
  EXPECT_GT(curr_bounds1.width(), final_bounds1.width());
  EXPECT_LT(curr_bounds2.width(), original_bounds2.width());
  EXPECT_GT(curr_bounds2.width(), final_bounds2.width());
  EXPECT_LT(curr_bounds3.width(), original_bounds3.width());
  EXPECT_GT(curr_bounds3.width(), final_bounds3.width());

  EXPECT_LT(curr_bounds1.height(), original_bounds1.height());
  EXPECT_GT(curr_bounds1.height(), final_bounds1.height());
  EXPECT_LT(curr_bounds2.height(), original_bounds2.height());
  EXPECT_GT(curr_bounds2.height(), final_bounds2.height());
  EXPECT_LT(curr_bounds3.height(), original_bounds3.height());
  EXPECT_GT(curr_bounds3.height(), final_bounds3.height());

  // Confirm the opacity of minimized windows is not 100%.
  float opacity = GetOverviewItemForWindow(minimized_window.get())
                      ->GetLeafItemForWindow(minimized_window.get())
                      ->item_widget()
                      ->GetLayer()
                      ->opacity();
  EXPECT_NE(opacity, 1.f);
  EXPECT_NE(opacity, 0.f);
}

// Tests that the opacity of the "No recent items" label is continuous.
TEST_P(ContinuousOverviewAnimationTest, NoRecentItemsLabel) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Start scrolling to enter overview. The no recent items label should have an
  // opacity between 0.f and 1.f and not be animating.
  const float short_scroll = 50.f;
  ThreeFingerScroll(0, short_scroll, /*complete_scroll=*/false);
  ASSERT_TRUE(InOverviewSession());

  views::Widget* no_windows_widget =
      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow())
          ->no_windows_widget();
  ASSERT_TRUE(no_windows_widget);

  ui::Layer* no_windows_layer = no_windows_widget->GetLayer();

  EXPECT_GT(no_windows_layer->opacity(), 0.f);
  EXPECT_LT(no_windows_layer->opacity(), 1.f);
  EXPECT_FALSE(no_windows_layer->GetAnimator()->is_animating());

  // Complete the enter overview scroll. The no recent items label should be
  // opaque.
  const float long_scroll = 500.f;
  ThreeFingerScroll(0, long_scroll, /*complete_scroll=*/false);
  EXPECT_EQ(1.f, no_windows_layer->opacity());

  ThreeFingerScroll(0, short_scroll, /*complete_scroll=*/true);
  EXPECT_EQ(1.f, no_windows_layer->opacity());
  WaitForOverviewEnterAnimation();
  ASSERT_TRUE(InOverviewSession());

  // Start scrolling to exit overview. The no recent items label should have an
  // opacity between 0.f and 1.f and not be animating.
  ThreeFingerScroll(0, -short_scroll, /*complete_scroll=*/false);
  EXPECT_GT(no_windows_layer->opacity(), 0.f);
  EXPECT_LT(no_windows_layer->opacity(), 1.f);
  EXPECT_FALSE(no_windows_layer->GetAnimator()->is_animating());
}

// Test that the rounded corners and shadows are shown at the correct times
// throughout a continuous scroll.
TEST_P(ContinuousOverviewAnimationTest, WindowCornerRadiiAndShadows) {
  std::unique_ptr<aura::Window> active_window(CreateTestWindow());
  std::unique_ptr<aura::Window> minimized_window(CreateTestWindow());
  WindowState::Get(minimized_window.get())->Minimize();

  // Swipe up a little bit and keep the fingers rested on the trackpad so
  // that the window placements are paused.
  const float short_scroll = 50.f;
  ThreeFingerScroll(0, short_scroll, /*complete_scroll=*/false);
  ASSERT_TRUE(InOverviewSession());

  auto* active_item = GetOverviewItemForWindow(active_window.get());
  auto* minimized_item = GetOverviewItemForWindow(minimized_window.get());

  // If a window is minimized, it should immediately show rounded corners.
  // Otherwise, retain sharp corners until the enter animation ends.
  EXPECT_FALSE(HasRoundedCorner(active_item));
  EXPECT_TRUE(HasRoundedCorner(minimized_item));

  // Shadows are hidden until the continuous swipe is over.
  EXPECT_TRUE(GetShadowBounds(active_item).IsEmpty());
  EXPECT_TRUE(GetShadowBounds(minimized_item).IsEmpty());

  // Reset.
  ToggleOverview();
  ASSERT_FALSE(InOverviewSession());

  // Give us some time to check the entry animation since we will be triggering
  // it by scrolling up and then lifting the fingers off of the trackpad.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Scroll up more than 50% of the threshold then let go of the trackpad.
  const float medium_scroll =
      (WmGestureHandler::kVerticalThresholdDp / 2.f) + 1.f;
  ThreeFingerScroll(0, medium_scroll, /*complete_scroll=*/true);

  // Get the overview items again since this is a new overview session.
  active_item = GetOverviewItemForWindow(active_window.get());
  minimized_item = GetOverviewItemForWindow(minimized_window.get());

  // Rounded corners are shown once the fingers lift. Shadows on minimized
  // windows are shown, but shadows on non-minimized windows are hidden until
  // the animation is finished.
  EXPECT_TRUE(HasRoundedCorner(active_item));
  EXPECT_TRUE(GetShadowBounds(active_item).IsEmpty());
  EXPECT_TRUE(HasRoundedCorner(minimized_item));
  EXPECT_FALSE(GetShadowBounds(minimized_item).IsEmpty());

  // Ensure overview has been entered completely.
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kEnterAnimationComplete);
  ASSERT_TRUE(InOverviewSession());

  // All items should have rounded corners and shadows.
  EXPECT_TRUE(HasRoundedCorner(active_item));
  EXPECT_TRUE(HasRoundedCorner(minimized_item));
  EXPECT_FALSE(GetShadowBounds(active_item).IsEmpty());
  EXPECT_FALSE(GetShadowBounds(minimized_item).IsEmpty());
}

// Tests that scrolls enter/exit overview mode as expected, regardless of the
// state of `NaturalScroll`.
TEST_P(ContinuousOverviewAnimationTest, ReverseGesturesTest) {
  const float long_scroll = 600.f;
  const float short_scroll = 50.f;
  ASSERT_FALSE(InOverviewSession());

  // Test an incorrect, complete, scroll.
  ThreeFingerScroll(0, -long_scroll, /*complete_scroll=*/true);
  ASSERT_FALSE(InOverviewSession());

  // Test a correct, complete, scroll.
  ThreeFingerScroll(0, long_scroll, /*complete_scroll=*/true);
  ASSERT_TRUE(InOverviewSession());

  // Test an incorrect, complete, scroll.
  ThreeFingerScroll(0, long_scroll, /*complete_scroll=*/true);
  ASSERT_TRUE(InOverviewSession());

  // Test a correct, complete, scroll.
  ThreeFingerScroll(0, -long_scroll, /*complete_scroll=*/true);
  ASSERT_FALSE(InOverviewSession());

  // Test an incorrect, incomplete, scroll.
  ThreeFingerScroll(0, -short_scroll, /*complete_scroll=*/false);
  ASSERT_FALSE(InOverviewSession());

  // Test a correct, incomplete, scroll.
  ThreeFingerScroll(0, short_scroll, /*complete_scroll=*/false);
  ASSERT_TRUE(InOverviewSession());
}

INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
                         ContinuousOverviewAnimationTest,
                         testing::Bool(),
                         [](const testing::TestParamInfo<bool>& info) {
                           return info.param ? "NaturalScrollOn"
                                             : "NaturalScrollOff";
                         });

// A unique test class for testing flings in overview as those rely on observing
// compositor animations which require a mock time task environment.
class OverviewSessionFlingTest : public AshTestBase {
 public:
  OverviewSessionFlingTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  ~OverviewSessionFlingTest() override = default;

  OverviewSessionFlingTest(const OverviewSessionFlingTest&) = delete;
  OverviewSessionFlingTest& operator=(const OverviewSessionFlingTest&) = delete;

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();

    // Overview flinging is only available in tablet mode.
    base::RunLoop().RunUntilIdle();
    TabletModeControllerTestApi().EnterTabletMode();
    base::RunLoop().RunUntilIdle();
  }
};

TEST_F(OverviewSessionFlingTest, BasicFling) {
  std::vector<std::unique_ptr<aura::Window>> windows(16);
  for (int i = 15; i >= 0; --i)
    windows[i] = CreateTestWindow();

  ToggleOverview();
  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
  OverviewGridEventHandler* grid_event_handler = grid->grid_event_handler();

  auto* item = GetOverviewItemForWindow(windows[2].get());
  const gfx::Point item_center =
      gfx::ToRoundedPoint(item->target_bounds().CenterPoint());

  // Create a scroll sequence which results in a fling.
  const gfx::Vector2d shift(-200, 0);
  GetEventGenerator()->GestureScrollSequence(item_center, item_center + shift,
                                             base::Milliseconds(10), 10);

  ui::Compositor* const compositor =
      windows[0]->GetRootWindow()->layer()->GetCompositor();
  ui::DrawWaiterForTest::WaitForCompositingStarted(compositor);
  ASSERT_TRUE(grid_event_handler->IsFlingInProgressForTesting());

  // Test that the scroll offset decreases as we advance the clock. Check the
  // scroll offset instead of the item bounds as there is an optimization which
  // does not update the item bounds of invisible elements. On some iterations,
  // there may not be enough time passed to decay the velocity so the scroll
  // offset will not change, but the overall change should be substantial.
  constexpr int kMaxLoops = 10;
  const float initial_scroll_offset = OverviewGridTestApi(grid).scroll_offset();
  float previous_scroll_offset = initial_scroll_offset;
  for (int i = 0;
       i < kMaxLoops && grid_event_handler->IsFlingInProgressForTesting();
       ++i) {
    task_environment()->FastForwardBy(base::Milliseconds(50));
    ui::DrawWaiterForTest::WaitForCompositingStarted(compositor);

    float scroll_offset = OverviewGridTestApi(grid).scroll_offset();
    EXPECT_LE(scroll_offset, previous_scroll_offset);
    previous_scroll_offset = scroll_offset;
  }

  EXPECT_LT(OverviewGridTestApi(grid).scroll_offset(),
            initial_scroll_offset - 100.f);
}

// Tests that a vertical scroll sequence will close the window it is scrolled
// on.
TEST_F(TabletModeOverviewSessionTest, VerticalScrollingOnOverviewItem) {
  constexpr int kNumWidgets = 8;
  std::vector<std::unique_ptr<views::Widget>> widgets(kNumWidgets);
  for (int i = kNumWidgets - 1; i >= 0; --i) {
    widgets[i] =
        CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
  }
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* leftmost_window =
      GetOverviewItemForWindow(widgets[0]->GetNativeWindow());
  const gfx::Point topleft_window_center =
      gfx::ToRoundedPoint(leftmost_window->target_bounds().CenterPoint());
  const gfx::Point end_point = topleft_window_center - gfx::Vector2d(0, 300);

  GenerateScrollSequence(topleft_window_center, end_point);
  EXPECT_TRUE(widgets[0]->IsClosed());
}

// Test that scrolling occurs if we hit the associated keyboard shortcut.
TEST_F(TabletModeOverviewSessionTest, CheckScrollingWithKeyboardShortcut) {
  auto windows = CreateAppWindows(9);
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  auto* leftmost_window = GetOverviewItemForWindow(windows[0].get());
  const gfx::RectF left_bounds = leftmost_window->target_bounds();

  PressAndReleaseKey(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN);
  EXPECT_LT(leftmost_window->target_bounds(), left_bounds);
}

// Test that tapping a window in overview closes overview mode.
TEST_F(TabletModeOverviewSessionTest, CheckWindowActivateOnTap) {
  base::UserActionTester user_action_tester;
  auto windows = CreateAppWindows(8);
  wm::ActivateWindow(windows[1].get());

  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());

  // Tap on |windows[1]| to exit overview.
  GetEventGenerator()->GestureTapAt(
      GetTransformedTargetBounds(windows[1].get()).CenterPoint());
  EXPECT_EQ(
      0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));

  // |windows[1]| remains active. Click on it to exit overview.
  ASSERT_EQ(windows[1].get(), window_util::GetFocusedWindow());
  ToggleOverview();
  ClickWindow(windows[1].get());
  EXPECT_EQ(
      0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));
}

TEST_F(TabletModeOverviewSessionTest, LayoutValidAfterRotation) {
  if (!features::IsForestFeatureEnabled()) {
    return;
  }

  UpdateDisplay("1366x768");
  display::test::ScopedSetInternalDisplayId set_internal(
      Shell::Get()->display_manager(),
      display::Screen::GetScreen()->GetPrimaryDisplay().id());
  auto windows = CreateAppWindows(9);

  // Helper to determine whether a grid layout is valid. It is considered valid
  // if the left edge of the first item is close enough to the left edge of the
  // grid bounds and if the right edge of the last item is close enough to the
  // right edge of the grid bounds. Either of these being false would mean there
  // is a large padding which shouldn't be there.
  auto layout_valid = [&windows, this](int expected_padding) {
    auto* first_item = GetOverviewItemForWindow(windows.front().get());
    auto* last_item = GetOverviewItemForWindow(windows.back().get());

    const gfx::Rect first_bounds =
        gfx::ToEnclosedRect(first_item->target_bounds());
    const gfx::Rect last_bounds =
        gfx::ToEnclosedRect(last_item->target_bounds());

    const gfx::Rect grid_bounds = GetGridBounds();
    const bool first_bounds_valid =
        first_bounds.x() <= (grid_bounds.x() + expected_padding);
    const bool last_bounds_valid =
        last_bounds.right() >= (grid_bounds.right() - expected_padding);
    return first_bounds_valid && last_bounds_valid;
  };

  // Enter overview and scroll to the edge of the grid. The layout should remain
  // valid.
  ToggleOverview();
  ASSERT_TRUE(InOverviewSession());
  // The expected padding should be the x position of the first item, before the
  // grid gets shifted.
  const int expected_padding =
      GetOverviewItemForWindow(windows.front().get())->target_bounds().x();
  GenerateScrollSequence(gfx::Point(1300, 10), gfx::Point(100, 10));
  EXPECT_TRUE(layout_valid(expected_padding));

  // Tests that the layout is still valid after a couple rotations.
  ScreenOrientationControllerTestApi test_api(
      Shell::Get()->screen_orientation_controller());
  test_api.SetDisplayRotation(display::Display::ROTATE_90,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_TRUE(layout_valid(expected_padding));

  test_api.SetDisplayRotation(display::Display::ROTATE_180,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_TRUE(layout_valid(expected_padding));
}

// Tests that windows snap through long press and drag to left or right side of
// the screen.
TEST_F(TabletModeOverviewSessionTest, DragOverviewWindowToSnap) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds));

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  ASSERT_FALSE(split_view_controller()->InSplitViewMode());

  // Dispatches a long press event at the |overview_item1|'s current location to
  // start dragging in SplitView. Drags |overview_item1| to the left border of
  // the screen. SplitView should trigger and upon completing drag,
  // |overview_item1| should snap to the left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  const gfx::PointF snap_left_location =
      gfx::PointF(GetGridBounds().left_center());

  DispatchLongPress(overview_item1);
  GetOverviewSession()->Drag(
      overview_item1,
      gfx::PointF(overview_item1->target_bounds().left_center()));
  GetOverviewSession()->CompleteDrag(overview_item1, snap_left_location);

  ASSERT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window1.get());

  // Dispatches a long press event at the |overview_item2|'s current location to
  // start dragging in SplitView. Drags |overview_item2| to the right border of
  // the screen. Upon completing drag, |overview_item2| should snap to the
  // right.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  const gfx::PointF snap_right_location =
      gfx::PointF(GetGridBounds().right_center());

  DispatchLongPress(overview_item2);
  GetOverviewSession()->Drag(
      overview_item2,
      gfx::PointF(overview_item2->target_bounds().right_center()));
  GetOverviewSession()->CompleteDrag(overview_item2, snap_right_location);

  EXPECT_FALSE(InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  EXPECT_EQ(split_view_controller()->secondary_window(), window2.get());
}

// Verify that if the window item has been dragged enough vertically, the window
// will be closed.
TEST_F(TabletModeOverviewSessionTest, DragToClose) {
  // This test requires a widget.
  std::unique_ptr<views::Widget> widget(
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET));

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  auto* item = GetOverviewItemForWindow(widget->GetNativeWindow());
  const gfx::PointF start = item->target_bounds().CenterPoint();
  ASSERT_TRUE(item);

  // This drag has not covered enough distance, so the widget is not closed and
  // we remain in overview mode.
  GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item);
  GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 80));
  GetOverviewSession()->CompleteDrag(item, start + gfx::Vector2dF(0, 80));
  ASSERT_TRUE(GetOverviewSession());

  // Verify that the second drag has enough vertical distance, so the widget
  // will be closed and overview mode will be exited.
  GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item);
  GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 180));
  GetOverviewSession()->CompleteDrag(item, start + gfx::Vector2dF(0, 180));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(GetOverviewSession());
  EXPECT_TRUE(widget->IsClosed());
}

// Verify that if the window item has been flung enough vertically, the window
// will be closed.
TEST_F(TabletModeOverviewSessionTest, FlingToClose) {
  // This test requires a widget.
  std::unique_ptr<views::Widget> widget(
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET));

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(1u, GetOverviewSession()->grid_list()[0]->GetNumWindows());

  auto* item = GetOverviewItemForWindow(widget->GetNativeWindow());
  const gfx::PointF start = item->target_bounds().CenterPoint();
  ASSERT_TRUE(item);

  // Verify that items flung horizontally do not close the item.
  GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item);
  GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 50));
  GetOverviewSession()->Fling(item, start, 2500, 0);
  ASSERT_TRUE(GetOverviewSession());

  // Verify that items flung vertically but without enough velocity do not
  // close the item.
  GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item);
  GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 50));
  GetOverviewSession()->Fling(item, start, 0, 1500);
  ASSERT_TRUE(GetOverviewSession());

  // Verify that flinging the item closes it, and since it is the last item in
  // overview mode, overview mode is exited.
  GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item);
  GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 50));
  GetOverviewSession()->Fling(item, start, 0, 2500);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(GetOverviewSession());
  EXPECT_TRUE(widget->IsClosed());
}

// Tests that nudging occurs in the most basic case, which is we have one row
// and one item which is about to be deleted by dragging. If the item is deleted
// we still only have one row, so the other items should nudge while the item is
// being dragged.
TEST_F(TabletModeOverviewSessionTest, BasicNudging) {
  // Set up three equal windows, which take up one row on the overview grid.
  // When one of them is deleted we are still left with all the windows on one
  // row.
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  std::unique_ptr<aura::Window> window3 = CreateTestWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());

  const gfx::RectF item1_bounds = item1->target_bounds();
  const gfx::RectF item2_bounds = item2->target_bounds();
  const gfx::RectF item3_bounds = item3->target_bounds();

  // Drag |item1| vertically. |item2| and |item3| bounds should change as they
  // should be nudging towards their final bounds.
  GetOverviewSession()->InitiateDrag(item1, item1_bounds.CenterPoint(),
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item1);
  GetOverviewSession()->Drag(
      item1, item1_bounds.CenterPoint() + gfx::Vector2dF(0, 160));
  EXPECT_NE(item2_bounds, item2->target_bounds());
  EXPECT_NE(item3_bounds, item3->target_bounds());

  // Drag |item1| back to its start drag location and release, so that it does
  // not get deleted.
  GetOverviewSession()->Drag(item1, item1_bounds.CenterPoint());
  GetOverviewSession()->CompleteDrag(item1, item1_bounds.CenterPoint());

  // Drag |item3| vertically. |item1| and |item2| bounds should change as they
  // should be nudging towards their final bounds.
  GetOverviewSession()->InitiateDrag(item3, item3_bounds.CenterPoint(),
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item3);
  GetOverviewSession()->Drag(
      item3, item3_bounds.CenterPoint() + gfx::Vector2dF(0, 160));
  EXPECT_NE(item1_bounds, item1->target_bounds());
  EXPECT_NE(item2_bounds, item2->target_bounds());
}

// Tests that no nudging occurs when the number of rows in overview mode change
// if the item to be deleted results in the overview grid to change number of
// rows.
TEST_F(TabletModeOverviewSessionTest, NoNudgingWhenNumRowsChange) {
  UpdateDisplay("800x700");

  // Set up four equal windows, which would split into two rows in overview
  // mode. Removing one window would leave us with three windows, which only
  // takes a single row in overview.
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  std::unique_ptr<aura::Window> window3 = CreateTestWindow();
  std::unique_ptr<aura::Window> window4 = CreateTestWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());
  auto* item4 = GetOverviewItemForWindow(window4.get());

  const gfx::RectF item1_bounds = item1->target_bounds();
  const gfx::RectF item2_bounds = item2->target_bounds();
  const gfx::RectF item3_bounds = item3->target_bounds();
  const gfx::RectF item4_bounds = item4->target_bounds();

  // Ensure there are two rows in overview.
  ASSERT_EQ(item1_bounds.y(), item2_bounds.y());
  ASSERT_EQ(item3_bounds.y(), item4_bounds.y());
  ASSERT_NE(item1_bounds.y(), item3_bounds.y());

  // Drag |item1| past the drag to swipe threshold. None of the other window
  // bounds should change, as none of them should be nudged.
  GetOverviewSession()->InitiateDrag(item1, item1_bounds.CenterPoint(),
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item1);
  GetOverviewSession()->Drag(
      item1, item1_bounds.CenterPoint() + gfx::Vector2dF(0, 160));
  EXPECT_EQ(item2_bounds, item2->target_bounds());
  EXPECT_EQ(item3_bounds, item3->target_bounds());
  EXPECT_EQ(item4_bounds, item4->target_bounds());
}

// Tests that no nudging occurs when the item to be deleted results in an item
// from the previous row to drop down to the current row, thus causing the items
// to the right of the item to be shifted right, which is visually unacceptable.
TEST_F(TabletModeOverviewSessionTest, NoNudgingWhenLastItemOnPreviousRowDrops) {
  UpdateDisplay("800x700");

  // Set up five equal windows, which would split into two rows in overview
  // mode. Removing one window would cause the rows to rearrange, with the third
  // item dropping down from the first row to the second row. Create the windows
  // backward so the the window indexs match the order seen in overview, as
  // overview windows are ordered by MRU.
  const int kWindows = 5;
  std::unique_ptr<aura::Window> windows[kWindows];
  for (int i = kWindows - 1; i >= 0; --i)
    windows[i] = CreateTestWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  OverviewItemBase* items[kWindows];
  gfx::RectF item_bounds[kWindows];
  for (int i = 0; i < kWindows; ++i) {
    items[i] = GetOverviewItemForWindow(windows[i].get());
    item_bounds[i] = items[i]->target_bounds();
  }

  // Drag the forth item past the drag to swipe threshold. None of the other
  // window bounds should change, as none of them should be nudged, because
  // deleting the fourth item will cause the third item to drop down from the
  // first row to the second.
  GetOverviewSession()->InitiateDrag(items[3], item_bounds[3].CenterPoint(),
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/items[3]);
  GetOverviewSession()->Drag(
      items[3], item_bounds[3].CenterPoint() + gfx::Vector2dF(0, 160));
  EXPECT_EQ(item_bounds[0], items[0]->target_bounds());
  EXPECT_EQ(item_bounds[1], items[1]->target_bounds());
  EXPECT_EQ(item_bounds[2], items[2]->target_bounds());
  EXPECT_EQ(item_bounds[4], items[4]->target_bounds());

  // Drag the fourth item back to its start drag location and release, so that
  // it does not get deleted.
  GetOverviewSession()->Drag(items[3], item_bounds[3].CenterPoint());
  GetOverviewSession()->CompleteDrag(items[3], item_bounds[3].CenterPoint());

  // Drag the first item past the drag to swipe threshold. The second and third
  // items should nudge as expected as there is no item dropping down to their
  // row. The fourth and fifth items should not nudge as they are in a different
  // row than the first item.
  GetOverviewSession()->InitiateDrag(items[0], item_bounds[0].CenterPoint(),
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/items[0]);
  GetOverviewSession()->Drag(
      items[0], item_bounds[0].CenterPoint() + gfx::Vector2dF(0, 160));
  EXPECT_NE(item_bounds[1], items[1]->target_bounds());
  EXPECT_NE(item_bounds[2], items[2]->target_bounds());
  EXPECT_EQ(item_bounds[3], items[3]->target_bounds());
  EXPECT_EQ(item_bounds[4], items[4]->target_bounds());
}

// Tests that there is no crash when destroying a window during a nudge drag.
// Regression test for https://crbug.com/997335.
TEST_F(TabletModeOverviewSessionTest, DestroyWindowDuringNudge) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  std::unique_ptr<aura::Window> window3 = CreateTestWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  auto* item = GetOverviewItemForWindow(window1.get());
  const gfx::PointF item_center = item->target_bounds().CenterPoint();

  // Drag |item1| vertically to start nudging.
  GetOverviewSession()->InitiateDrag(item, item_center,
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/item);
  GetOverviewSession()->Drag(item, item_center + gfx::Vector2dF(0, 160));

  // Destroy |window2| and |window3|,then keep dragging. There should be no
  // crash.
  window2.reset();
  window3.reset();
  GetOverviewSession()->Drag(item, item_center + gfx::Vector2dF(0, 260));
}

TEST_F(TabletModeOverviewSessionTest, MultiTouch) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  // Dispatches a long press event to start drag mode.
  auto* item = GetOverviewItemForWindow(window1.get());
  DispatchLongPress(item);
  GetOverviewSession()->Drag(item, gfx::PointF(10.f, 500.f));
  const gfx::Rect item_bounds = item->GetWindow()->GetBoundsInScreen();

  // Tap on a point on the wallpaper. Normally this would exit overview, but not
  // while a drag is underway.
  GetEventGenerator()->set_current_screen_location(gfx::Point(10, 10));
  GetEventGenerator()->PressTouch();
  GetEventGenerator()->ReleaseTouch();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(item_bounds, item->GetWindow()->GetBoundsInScreen());

  // Long press on another item, the bounds of both items should be unchanged.
  auto* item2 = GetOverviewItemForWindow(window2.get());
  const gfx::Rect item2_bounds = item2->GetWindow()->GetBoundsInScreen();
  DispatchLongPress(item2);
  EXPECT_EQ(item_bounds, item->GetWindow()->GetBoundsInScreen());
  EXPECT_EQ(item2_bounds, item2->GetWindow()->GetBoundsInScreen());

  // Clicking on a point on the wallpaper should still exit overview.
  GetEventGenerator()->set_current_screen_location(gfx::Point(10, 10));
  GetEventGenerator()->ClickLeftButton();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
}

// Tests that when exiting overview in a way that causes windows to minimize,
// rounded corners are removed, otherwise they will be visible after
// unminimizing. Regression test for https://crbug.com/1146240.
TEST_F(TabletModeOverviewSessionTest, MinimizedRoundedCorners) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window(CreateTestWindow(bounds));

  // Enter overview. Spin the run loop since rounded corners are applied on a
  // post task.
  ToggleOverview();
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  // Tap on a point on the wallpaper to minimize the window and exit overview.
  GetEventGenerator()->set_current_screen_location(gfx::Point(10, 10));
  GetEventGenerator()->ClickLeftButton();

  // Tests that the window layer has rounded corners removed after exiting
  // overview.
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
  EXPECT_EQ(gfx::RoundedCornersF(), window->layer()->rounded_corner_radii());
}

// Tests the UAF issue reported in b/301368132 has been fixed. The overview
// item in `OverviewWindowDragController::CompleteDrag()` may be reset in
// `OverviewGrid::RemoveItem()` and is accessed again when getting the
// window for `ScopedFloatContainerStacker::OnDragFinished()`.
TEST_F(TabletModeOverviewSessionTest, AvoidUaFOnCompleteDrag) {
  std::unique_ptr<aura::Window> window = CreateAppWindow(gfx::Rect(100, 100));
  WindowState* window_state = WindowState::Get(window.get());
  const WindowSnapWMEvent snap_type(WM_EVENT_SNAP_PRIMARY);
  window_state->OnWMEvent(&snap_type);
  EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
            window_state->GetStateType());
  OverviewController* overview_controller = OverviewController::Get();
  EXPECT_TRUE(overview_controller->InOverviewSession());

  window_state->Minimize();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  auto* overview_item = GetOverviewItemForWindow(window.get());

  // Trigger `OverviewWindowDragController::CompleteDrag()` and verify that
  // there will be no crash.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint()));
  event_generator->ClickLeftButton();
  EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
            window_state->GetStateType());
}

// Test the split view and overview functionalities in tablet mode.
class SplitViewOverviewSessionTest : public OverviewTestBase {
 public:
  SplitViewOverviewSessionTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kSnapGroup,
                              features::kOsSettingsRevampWayfinding,
                              features::kDeskBarWindowOcclusionOptimization},
        /*disabled_features=*/{});
  }

  SplitViewOverviewSessionTest(const SplitViewOverviewSessionTest&) = delete;
  SplitViewOverviewSessionTest& operator=(const SplitViewOverviewSessionTest&) =
      delete;

  ~SplitViewOverviewSessionTest() override = default;

  enum class SelectorItemLocation {
    CENTER,
    ORIGIN,
    TOP_RIGHT,
    BOTTOM_RIGHT,
    BOTTOM_LEFT,
  };

  void SetUp() override {
    OverviewTestBase::SetUp();
    EnterTabletMode();
  }

  SplitViewController* split_view_controller() {
    return SplitViewController::Get(Shell::GetPrimaryRootWindow());
  }

  SplitViewDivider* split_view_divider() {
    return split_view_controller()->split_view_divider();
  }

  bool IsDividerAnimating() {
    return split_view_controller()->IsDividerAnimating();
  }

  void SkipDividerSnapAnimation() {
    if (!IsDividerAnimating())
      return;
    split_view_controller()->StopAndShoveAnimatedDivider();
    split_view_controller()->EndResizeWithDividerImpl();
    split_view_controller()->EndSplitViewAfterResizingAtEdgeIfAppropriate();
  }

  void EndSplitView() { split_view_controller()->EndSplitView(); }

  void CheckWindowResizingPerformanceHistograms(
      const char* trace,
      int with_empty_overview_grid,
      int max_latency_with_empty_overview_grid,
      int with_nonempty_overview_grid,
      int max_latency_with_nonempty_overview_grid) {
    CheckForDuplicateTraceName(trace);
    SCOPED_TRACE(trace);

    histograms_.ExpectTotalCount(
        "Ash.SplitViewResize.PresentationTime.ClamshellMode.SingleWindow",
        with_empty_overview_grid);
    histograms_.ExpectTotalCount(
        "Ash.SplitViewResize.PresentationTime.MaxLatency.ClamshellMode."
        "SingleWindow",
        max_latency_with_empty_overview_grid);
    histograms_.ExpectTotalCount(
        "Ash.SplitViewResize.PresentationTime.ClamshellMode.WithOverview",
        with_nonempty_overview_grid);
    histograms_.ExpectTotalCount(
        "Ash.SplitViewResize.PresentationTime.MaxLatency.ClamshellMode."
        "WithOverview",
        max_latency_with_nonempty_overview_grid);
  }

 protected:
  aura::Window* CreateWindow(const gfx::Rect& bounds) {
    aura::Window* window = CreateTestWindowInShellWithDelegate(
        aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), -1,
        bounds);
    return window;
  }

  aura::Window* CreateWindowWithMinimumSize(const gfx::Rect& bounds,
                                            const gfx::Size& size) {
    auto* delegate =
        aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
    aura::Window* window =
        CreateTestWindowInShellWithDelegate(delegate, -1, bounds);
    delegate->set_minimum_size(size);
    return window;
  }

  // Returns the expected overview bounds including the hotseat inset. See
  // `ShrinkBoundsByHotseatInset()`.
  // TODO(sophiewen): Refactor this for both `SplitViewOverviewSessionTest`
  // and `FasterSplitScreenSetupTest` and make this work for multi-display.
  gfx::Rect GetExpectedOverviewBounds() {
    aura::Window* root_window = Shell::GetPrimaryRootWindow();
    gfx::Rect overview_bounds(GetWorkAreaInScreen(root_window));

    if (auto* split_view_drag_indicators =
            GetOverviewGridForRoot(root_window)->split_view_drag_indicators();
        split_view_drag_indicators) {
      // If we are dragging to snap, `SplitViewOverviewSession` is not active
      // yet, but the overview grid bounds are split.
      gfx::Rect left_bounds, right_bounds;
      overview_bounds.SplitVertically(left_bounds, right_bounds);
      // If we are dragging to snap in tablet mode, `split_view_divider` hasn't
      // been created yet, but we still need to subtract the divider width.
      const int divider_width = display::Screen::GetScreen()->InTabletMode()
                                    ? kSplitviewDividerShortSideLength / 2
                                    : 0;
      switch (split_view_drag_indicators->current_window_dragging_state()) {
        case SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary:
          // If we are dragging to snap left, the grid bounds are on the right.
          right_bounds.set_x(right_bounds.x() + divider_width);
          right_bounds.set_width(right_bounds.width() - divider_width);
          return right_bounds;
        case SplitViewDragIndicators::WindowDraggingState::kToSnapSecondary:
          // If we are dragging to snap right, the grid bounds are on the left.
          left_bounds.set_width(left_bounds.width() - divider_width);
          return left_bounds;
        case SplitViewDragIndicators::WindowDraggingState::kNoDrag:
          break;
        case SplitViewDragIndicators::WindowDraggingState::kOtherDisplay:
        case SplitViewDragIndicators::WindowDraggingState::kFromOverview:
        case SplitViewDragIndicators::WindowDraggingState::kFromTop:
        case SplitViewDragIndicators::WindowDraggingState::kFromShelf:
        case SplitViewDragIndicators::WindowDraggingState::kFromFloat:
          NOTREACHED();
      }
    }

    return GetGridBoundsInScreen(root_window);
  }

  gfx::Rect GetSplitViewDividerBounds(bool is_dragging) {
    if (!split_view_controller()->InTabletSplitViewMode()) {
      return gfx::Rect();
    }
    return split_view_controller()
        ->split_view_divider()
        ->GetDividerBoundsInScreen(is_dragging);
  }

  gfx::Rect GetWorkAreaInScreen(aura::Window* window) {
    return screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
        window);
  }

  // Drags a overview item |item| from its center or one of its corners
  // to |end_location|. This should be used over
  // DragWindowTo(OverviewItem*, gfx::Point) when testing snapping a
  // window, but the windows centerpoint may be inside a snap region, thus the
  // window will not snapped. This function is mostly used to test splitview so
  // |long_press| is default to true. Set |long_press| to false if we do not
  // want to long press after every press, which enables dragging vertically to
  // close an item.
  void DragWindowTo(OverviewItemBase* item,
                    const gfx::PointF& end_location,
                    SelectorItemLocation location,
                    bool long_press = true) {
    gfx::PointF start_location;
    switch (location) {
      case SelectorItemLocation::CENTER:
        start_location = item->target_bounds().CenterPoint();
        break;
      case SelectorItemLocation::ORIGIN:
        start_location = item->target_bounds().origin();
        break;
      case SelectorItemLocation::TOP_RIGHT:
        start_location = item->target_bounds().top_right();
        break;
      case SelectorItemLocation::BOTTOM_RIGHT:
        start_location = item->target_bounds().bottom_right();
        break;
      case SelectorItemLocation::BOTTOM_LEFT:
        start_location = item->target_bounds().bottom_left();
        break;
      default:
        NOTREACHED();
    }
    GetOverviewSession()->InitiateDrag(item, start_location,
                                       /*is_touch_dragging=*/true,
                                       /*event_source_item=*/item);
    if (long_press)
      GetOverviewSession()->StartNormalDragMode(start_location);
    GetOverviewSession()->Drag(item, end_location);
    GetOverviewSession()->CompleteDrag(item, end_location);
  }

  // Drags a overview item |item| from its center point to |end_location|.
  void DragWindowTo(OverviewItemBase* item, const gfx::PointF& end_location) {
    DragWindowTo(item, end_location, SelectorItemLocation::CENTER, true);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests that dragging an overview item to the edge of the screen snaps the
// window. If two windows are snapped to left and right side of the screen, exit
// the overview mode.
TEST_F(SplitViewOverviewSessionTest, DragOverviewWindowToSnap) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));

  ToggleOverview();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));

  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window1.get());

  // Drag |window2| selector item to attempt to snap to left. Since there is
  // already one left snapped window |window1|, |window1| will be put in
  // overview mode.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  DragWindowTo(overview_item2, gfx::PointF(0, 0));

  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window2.get());
  EXPECT_TRUE(GetOverviewController()->overview_session()->IsWindowInOverview(
      window1.get()));

  // Drag |window3| selector item to snap to right.
  auto* overview_item3 = GetOverviewItemForWindow(window3.get());
  const gfx::PointF end_location3(GetWorkAreaInScreen(window3.get()).width(),
                                  0.f);
  DragWindowTo(overview_item3, end_location3);

  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  EXPECT_EQ(split_view_controller()->secondary_window(), window3.get());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
}

// Regression test for http://b/323136574, where a floated window should not
// have an unclipped size when it's in a partial overview session.
TEST_F(SplitViewOverviewSessionTest, FloatedWindowsHaveNoUnclippedSize) {
  std::unique_ptr<aura::Window> window1 = CreateAppWindow();
  std::unique_ptr<aura::Window> window2 = CreateAppWindow();

  // Float `window1` and then snap `window2`. A partial overview session should
  // start.
  Shell::Get()->float_controller()->ToggleFloat(window1.get());
  EXPECT_TRUE(WindowState::Get(window1.get())->IsFloated());

  const WindowSnapWMEvent event(
      WM_EVENT_CYCLE_SNAP_SECONDARY,
      WindowSnapActionSource::kKeyboardShortcutToSnap);
  auto* window2_state = WindowState::Get(window2.get());
  window2_state->OnWMEvent(&event);
  EXPECT_TRUE(window2_state->IsSnapped());

  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  auto* window1_item = GetOverviewItemForWindow(window1.get());
  ASSERT_TRUE(window1_item);

  EXPECT_FALSE(window1_item->unclipped_size_for_testing());
}

// Verify the correct behavior when dragging windows in overview mode.
TEST_F(SplitViewOverviewSessionTest, OverviewDragControllerBehavior) {
  ui::GestureConfiguration* gesture_config =
      ui::GestureConfiguration::GetInstance();
  gesture_config->set_long_press_time_in_ms(1);
  gesture_config->set_short_press_time(base::Milliseconds(1));
  gesture_config->set_show_press_delay_in_ms(1);

  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  auto* window_item1 = GetOverviewItemForWindow(window1.get());
  auto* window_item2 = GetOverviewItemForWindow(window2.get());

  // Verify that if a drag is orginally horizontal, the drag behavior is drag to
  // snap.
  using DragBehavior = OverviewWindowDragController::DragBehavior;
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(window_item1->target_bounds().CenterPoint()));
  generator->PressTouch();

  // Simulate a long press, which is required to snap windows.
  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2));
  run_loop.Run();

  OverviewWindowDragController* drag_controller =
      GetOverviewSession()->window_drag_controller();
  ASSERT_TRUE(drag_controller);
  EXPECT_EQ(DragBehavior::kNormalDrag,
            drag_controller->current_drag_behavior_for_testing());
  generator->MoveTouchBy(20, 0);
  EXPECT_EQ(DragBehavior::kNormalDrag,
            drag_controller->current_drag_behavior_for_testing());
  generator->ReleaseTouch();
  EXPECT_EQ(DragBehavior::kNoDrag,
            drag_controller->current_drag_behavior_for_testing());

  // Verify that if a drag is orginally vertical, the drag behavior is drag to
  // close.
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(window_item2->target_bounds().CenterPoint()));
  generator->PressTouch();

  // Use small increments otherwise a fling event will be fired.
  for (int j = 0; j < 20; ++j)
    generator->MoveTouchBy(0, 1);

  // A new instance of drag controller gets created each time a drag starts.
  drag_controller = GetOverviewSession()->window_drag_controller();
  EXPECT_EQ(DragBehavior::kDragToClose,
            drag_controller->current_drag_behavior_for_testing());
}

// Verify the window grid size changes as expected when dragging items around in
// overview mode when split view is enabled.
TEST_F(SplitViewOverviewSessionTest,
       OverviewGridSizeWhileDraggingWithSplitView) {
  // Add three windows and enter overview mode.
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> window3(CreateTestWindow());

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  // Select window one and start the drag.
  const int window_width =
      Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen().width();
  auto* overview_item = GetOverviewItemForWindow(window1.get());
  gfx::RectF overview_item_bounds = overview_item->target_bounds();
  gfx::PointF start_location(overview_item_bounds.CenterPoint());
  GetOverviewSession()->InitiateDrag(overview_item, start_location,
                                     /*is_touch_dragging=*/false,
                                     /*event_source_item=*/overview_item);

  // Verify that when dragged to the left, the window grid is located where the
  // right window of split view mode should be.
  const gfx::PointF left(0, 0);
  GetOverviewSession()->Drag(overview_item, left);
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(SplitViewController::State::kNoSnap,
            split_view_controller()->state());
  EXPECT_TRUE(split_view_controller()->primary_window() == nullptr);
  EXPECT_EQ(GetExpectedOverviewBounds(), GetGridBounds());

  // Verify that when dragged to the right, the window grid is located where the
  // left window of split view mode should be.
  const gfx::PointF right(window_width, 0);
  GetOverviewSession()->Drag(overview_item, right);
  EXPECT_EQ(SplitViewController::State::kNoSnap,
            split_view_controller()->state());
  EXPECT_TRUE(split_view_controller()->secondary_window() == nullptr);
  EXPECT_EQ(GetExpectedOverviewBounds(), GetGridBounds());

  // Verify that when dragged to the center, the window grid is has the
  // dimensions of the work area.
  const gfx::PointF center(window_width / 2, 0);
  GetOverviewSession()->Drag(overview_item, center);
  EXPECT_EQ(SplitViewController::State::kNoSnap,
            split_view_controller()->state());
  EXPECT_EQ(GetWorkAreaInScreen(window1.get()), GetGridBounds());

  // Snap window1 to the left and initialize dragging for window2.
  GetOverviewSession()->Drag(overview_item, left);
  GetOverviewSession()->CompleteDrag(overview_item, left);
  ASSERT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  ASSERT_EQ(window1.get(), split_view_controller()->primary_window());
  overview_item = GetOverviewItemForWindow(window2.get());
  overview_item_bounds = overview_item->target_bounds();
  start_location = overview_item_bounds.CenterPoint();
  GetOverviewSession()->InitiateDrag(overview_item, start_location,
                                     /*is_touch_dragging=*/false,
                                     /*event_source_item=*/overview_item);

  // Verify that when there is a snapped window, the window grid bounds remain
  // constant despite overview items being dragged left and right.
  GetOverviewSession()->Drag(overview_item, left);
  const gfx::Rect expected_grid_bounds = GetExpectedOverviewBounds();
  EXPECT_EQ(expected_grid_bounds, GetGridBounds());
  GetOverviewSession()->Drag(overview_item, right);
  EXPECT_EQ(expected_grid_bounds, GetGridBounds());
  GetOverviewSession()->Drag(overview_item, center);
  EXPECT_EQ(expected_grid_bounds, GetGridBounds());
}

// Tests dragging a unsnappable window.
TEST_F(SplitViewOverviewSessionTest, DraggingUnsnappableAppWithSplitView) {
  std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();

  // The grid bounds should be the size of the root window minus the shelf.
  const gfx::Rect root_window_bounds =
      Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen();
  const gfx::Rect shelf_bounds =
      Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())->GetIdealBounds();
  const gfx::Rect expected_grid_bounds =
      SubtractRects(root_window_bounds, shelf_bounds);

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  // Verify that after dragging the unsnappable window to the left and right,
  // the window grid bounds do not change.
  auto* overview_item = GetOverviewItemForWindow(unsnappable_window.get());
  GetOverviewSession()->InitiateDrag(
      overview_item, overview_item->target_bounds().CenterPoint(),
      /*is_touch_dragging=*/false, /*event_source_item=*/overview_item);
  GetOverviewSession()->Drag(overview_item, gfx::PointF());
  EXPECT_EQ(expected_grid_bounds, GetGridBounds());
  GetOverviewSession()->Drag(overview_item,
                             gfx::PointF(root_window_bounds.right(), 0.f));
  EXPECT_EQ(expected_grid_bounds, GetGridBounds());
  GetOverviewSession()->Drag(
      overview_item, gfx::PointF(root_window_bounds.right() / 2.f, 0.f));
  EXPECT_EQ(expected_grid_bounds, GetGridBounds());
}

// Test that if an unsnappable window is dragged from overview to where another
// window is already snapped, then there is no snap preview, and if the drag
// ends there, then there is no DCHECK failure (or crash).
TEST_F(SplitViewOverviewSessionTest,
       DragUnsnappableWindowFromOverviewToSnappedWindow) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();
  ToggleOverview();
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  ASSERT_EQ(1u, GetOverviewSession()->grid_list().size());
  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  auto* overview_item =
      overview_grid->GetOverviewItemContaining(unsnappable_window.get());
  GetOverviewSession()->InitiateDrag(
      overview_item, overview_item->target_bounds().CenterPoint(),
      /*is_touch_dragging=*/false, /*event_source_item=*/overview_item);
  GetOverviewSession()->Drag(overview_item, gfx::PointF());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
            overview_grid->split_view_drag_indicators()
                ->current_window_dragging_state());
  GetOverviewSession()->CompleteDrag(overview_item, gfx::PointF());
}

TEST_F(SplitViewOverviewSessionTest, Clipping) {
  // Helper to check if two rectangles have roughly the same aspect ratio. They
  // may be off by a bit due to insets but should have roughly the same shape.
  auto aspect_ratio_near = [](const gfx::Rect& rect1, const gfx::Rect& rect2) {
    DCHECK_GT(rect1.height(), 0);
    DCHECK_GT(rect2.height(), 0);
    constexpr float kEpsilon = 0.07f;
    const float rect1_aspect_ratio =
        static_cast<float>(rect1.width()) / rect1.height();
    const float rect2_aspect_ratio =
        static_cast<float>(rect2.width()) / rect2.height();
    return std::abs(rect2_aspect_ratio - rect1_aspect_ratio) < kEpsilon;
  };

  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  std::unique_ptr<aura::Window> window3 = CreateTestWindow();  // Minimized.
  std::unique_ptr<aura::Window> window4 = CreateTestWindow();  // Has top inset.
  WindowState::Get(window3.get())->Minimize();
  window4->SetProperty(aura::client::kTopViewInset, 32);

  for (bool portrait : {false, true}) {
    SCOPED_TRACE(portrait ? "Portrait" : "Landscape");
    if (portrait) {
      ScreenOrientationControllerTestApi test_api(
          Shell::Get()->screen_orientation_controller());
      test_api.SetDisplayRotation(display::Display::ROTATE_90,
                                  display::Display::RotationSource::ACTIVE);
    }

    const gfx::Rect clipping1 = window1->layer()->clip_rect();
    const gfx::Rect clipping2 = window2->layer()->clip_rect();
    const gfx::Rect clipping3 = window3->layer()->clip_rect();
    const gfx::Rect clipping4 = window4->layer()->clip_rect();
    const gfx::Rect maximized_bounds =
        screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
            window1.get());
    const gfx::Rect split_view_bounds_right =
        split_view_controller()->GetSnappedWindowBoundsInScreen(
            SnapPosition::kSecondary,
            /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
            /*account_for_divider_width=*/true);

    ToggleOverview();

    // Tests that after entering overview, windows with no top inset and
    // minimized windows still have no clip.
    ASSERT_TRUE(GetOverviewController()->InOverviewSession());
    EXPECT_EQ(clipping1, window1->layer()->clip_rect());
    EXPECT_EQ(clipping2, window2->layer()->clip_rect());
    EXPECT_EQ(clipping3, window3->layer()->clip_rect());
    EXPECT_NE(clipping4, window4->layer()->clip_rect());
    const gfx::Rect overview_clipping4 = window4->layer()->clip_rect();

    auto* item1 = GetOverviewItemForWindow(window1.get());
    auto* item2 = GetOverviewItemForWindow(window2.get());
    auto* item3 = GetOverviewItemForWindow(window3.get());
    auto* item4 = GetOverviewItemForWindow(window4.get());
    GetOverviewSession()->InitiateDrag(
        item1, item1->target_bounds().CenterPoint(),
        /*is_touch_dragging=*/false, /*event_source_item=*/item1);

    // Tests that after we drag to a preview area, the items target bounds have
    // a matching aspect ratio to what the window would have if it were to be
    // snapped in splitview. The window clipping should match this, but the
    // windows regular bounds remain unchanged (maximized).
    GetOverviewSession()->Drag(item1, gfx::PointF());
    EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
              GetOverviewSession()
                  ->grid_list()[0]
                  ->split_view_drag_indicators()
                  ->current_window_dragging_state());
    EXPECT_FALSE(window2->layer()->clip_rect().IsEmpty());
    EXPECT_TRUE(aspect_ratio_near(window2->layer()->clip_rect(),
                                  split_view_bounds_right));
    EXPECT_TRUE(aspect_ratio_near(
        gfx::ToEnclosedRect(item2->GetTargetBoundsWithInsets()),
        split_view_bounds_right));
    EXPECT_TRUE(
        aspect_ratio_near(window2->GetBoundsInScreen(), maximized_bounds));

    // The actual window of a minimized window should not be clipped. The
    // clipped layer will be the WindowPreviewView of the associated
    // OverviewItemView.
    EXPECT_TRUE(window3->layer()->clip_rect().IsEmpty());
    ui::Layer* preview_layer = item3->GetLeafItemForWindow(window3.get())
                                   ->overview_item_view()
                                   ->preview_view()
                                   ->layer();
    EXPECT_FALSE(preview_layer->clip_rect().IsEmpty());
    EXPECT_FALSE(preview_layer->transform().IsIdentity());
    // The clip rect is affected by |preview_layer|'s transform so apply it.
    const gfx::Rect clip_rects3 =
        preview_layer->transform().MapRect(preview_layer->clip_rect());
    EXPECT_TRUE(aspect_ratio_near(clip_rects3, split_view_bounds_right));
    EXPECT_TRUE(aspect_ratio_near(
        gfx::ToEnclosedRect(item3->GetTargetBoundsWithInsets()),
        split_view_bounds_right));
    EXPECT_TRUE(
        aspect_ratio_near(window3->GetBoundsInScreen(), maximized_bounds));

    // A window with top view inset should be clipped, but with a new clipping
    // than the original overview clipping.
    EXPECT_FALSE(window4->layer()->clip_rect().IsEmpty());
    EXPECT_NE(overview_clipping4, window4->layer()->clip_rect());
    EXPECT_TRUE(aspect_ratio_near(window4->layer()->clip_rect(),
                                  split_view_bounds_right));
    EXPECT_TRUE(aspect_ratio_near(
        gfx::ToEnclosedRect(item4->GetTargetBoundsWithInsets()),
        split_view_bounds_right));
    EXPECT_TRUE(
        aspect_ratio_near(window4->GetBoundsInScreen(), maximized_bounds));

    // Tests that after snapping, the aspect ratios should be the same as being
    // in the preview area.
    GetOverviewSession()->CompleteDrag(item1, gfx::PointF());
    ASSERT_EQ(SplitViewController::State::kPrimarySnapped,
              split_view_controller()->state());
    EXPECT_FALSE(window2->layer()->clip_rect().IsEmpty());
    EXPECT_TRUE(aspect_ratio_near(window2->layer()->clip_rect(),
                                  split_view_bounds_right));
    EXPECT_TRUE(aspect_ratio_near(
        gfx::ToEnclosedRect(item2->GetTargetBoundsWithInsets()),
        split_view_bounds_right));
    EXPECT_TRUE(
        aspect_ratio_near(window2->GetBoundsInScreen(), maximized_bounds));

    EXPECT_TRUE(window3->layer()->clip_rect().IsEmpty());
    EXPECT_TRUE(aspect_ratio_near(clip_rects3, split_view_bounds_right));
    EXPECT_TRUE(aspect_ratio_near(
        gfx::ToEnclosedRect(item3->GetTargetBoundsWithInsets()),
        split_view_bounds_right));
    EXPECT_TRUE(
        aspect_ratio_near(window3->GetBoundsInScreen(), maximized_bounds));

    EXPECT_FALSE(window4->layer()->clip_rect().IsEmpty());
    EXPECT_NE(overview_clipping4, window4->layer()->clip_rect());
    EXPECT_TRUE(aspect_ratio_near(window4->layer()->clip_rect(),
                                  split_view_bounds_right));
    EXPECT_TRUE(aspect_ratio_near(
        gfx::ToEnclosedRect(item4->GetTargetBoundsWithInsets()),
        split_view_bounds_right));
    EXPECT_TRUE(
        aspect_ratio_near(window4->GetBoundsInScreen(), maximized_bounds));

    // Tests that the clipping is reset after exiting overview.
    EndSplitView();
    ToggleOverview();
    EXPECT_EQ(clipping1, window1->layer()->clip_rect());
    EXPECT_EQ(clipping2, window2->layer()->clip_rect());
    EXPECT_EQ(clipping3, window3->layer()->clip_rect());
    EXPECT_EQ(clipping4, window4->layer()->clip_rect());
  }
}

// Tests that when splitview is inactive, there is no need for aspect ratio
// changes, so there is no clipping on the overview windows. Regression test for
// crbug.com/1020440.
TEST_F(SplitViewOverviewSessionTest, NoClippingWhenSplitviewDisabled) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();

  // Splitview is disabled when ChromeVox is enabled.
  Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
      true, A11Y_NOTIFICATION_NONE);
  ASSERT_FALSE(ShouldAllowSplitView());
  const gfx::Rect clipping1 = window1->layer()->clip_rect();
  const gfx::Rect clipping2 = window2->layer()->clip_rect();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(clipping1, window1->layer()->clip_rect());
  EXPECT_EQ(clipping2, window2->layer()->clip_rect());

  // Drag to the edge of the screen. There should be no clipping and no crash.
  auto* item1 = GetOverviewItemForWindow(window1.get());
  GetOverviewSession()->InitiateDrag(
      item1, item1->target_bounds().CenterPoint(),
      /*is_touch_dragging=*/false, /*event_source_item=*/item1);
  GetOverviewSession()->Drag(item1, gfx::PointF());
  EXPECT_EQ(clipping1, window1->layer()->clip_rect());
  EXPECT_EQ(clipping2, window2->layer()->clip_rect());
}

// Tests that if there is only one window in the MRU window list in the overview
// mode, snapping the window to one side of the screen will not end the overview
// mode even if there is no more window left in the overview window grid.
TEST_F(SplitViewOverviewSessionTest, EmptyWindowsListNotExitOverview) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));

  ToggleOverview();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());

  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());

  // Test that overview mode is active in this single window case.
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());

  // Create a new window should exit the overview mode.
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  wm::ActivateWindow(window2.get());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  // If there are only 2 snapped windows, close one of them should enter
  // overview mode.
  window2.reset();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());

  // If there are more than 2 windows in overview
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window4(CreateWindow(bounds));
  wm::ActivateWindow(window3.get());
  wm::ActivateWindow(window4.get());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  ToggleOverview();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  window3.reset();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  window4.reset();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());

  // Test that if there is only 1 snapped window, and no window in the overview
  // grid, ToggleOverview() can't end overview.
  ToggleOverview();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());

  EndSplitView();
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());

  // Test that ToggleOverview() can end overview if we're not in split view
  // mode.
  ToggleOverview();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());

  // Now enter overview and split view again. Test that exiting tablet mode can
  // end split view and overview correctly.
  ToggleOverview();
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());

  // Test that closing all windows in overview can end overview if we're not in
  // split view mode.
  ToggleOverview();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  window1.reset();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
}

// Tests using Alt+[ on a maximized window.
TEST_F(SplitViewOverviewSessionTest, AltLeftSquareBracketOnMaximizedWindow) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
  wm::ActivateWindow(snapped_window.get());
  WindowState* snapped_window_state = WindowState::Get(snapped_window.get());
  EXPECT_EQ(WindowStateType::kMaximized, snapped_window_state->GetStateType());
  EXPECT_EQ(SplitViewController::State::kNoSnap,
            split_view_controller()->state());
  EXPECT_FALSE(InOverviewSession());
  const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY);
  snapped_window_state->OnWMEvent(&alt_left_square_bracket);
  EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get()));
  EXPECT_EQ(WindowStateType::kPrimarySnapped,
            snapped_window_state->GetStateType());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  EXPECT_EQ(snapped_window.get(), split_view_controller()->primary_window());
  EXPECT_TRUE(InOverviewSession());
}

// Tests using Alt+] on a maximized window.
TEST_F(SplitViewOverviewSessionTest, AltRightSquareBracketOnMaximizedWindow) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
  wm::ActivateWindow(snapped_window.get());
  WindowState* snapped_window_state = WindowState::Get(snapped_window.get());
  EXPECT_EQ(WindowStateType::kMaximized, snapped_window_state->GetStateType());
  EXPECT_EQ(SplitViewController::State::kNoSnap,
            split_view_controller()->state());
  EXPECT_FALSE(InOverviewSession());
  const WindowSnapWMEvent alt_right_square_bracket(
      WM_EVENT_CYCLE_SNAP_SECONDARY);
  snapped_window_state->OnWMEvent(&alt_right_square_bracket);
  EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get()));
  EXPECT_EQ(WindowStateType::kSecondarySnapped,
            snapped_window_state->GetStateType());
  EXPECT_EQ(SplitViewController::State::kSecondarySnapped,
            split_view_controller()->state());
  EXPECT_EQ(snapped_window.get(), split_view_controller()->secondary_window());
  EXPECT_TRUE(InOverviewSession());
}

// Tests using Alt+[ and Alt+] on an unsnappable window.
TEST_F(SplitViewOverviewSessionTest, AltSquareBracketOnUnsnappableWindow) {
  std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();
  std::unique_ptr<aura::Window> other_window = CreateTestWindow();
  wm::ActivateWindow(unsnappable_window.get());
  WindowState* unsnappable_window_state =
      WindowState::Get(unsnappable_window.get());
  const auto expect_unsnappable_window_is_active_and_maximized =
      [this, &unsnappable_window, unsnappable_window_state]() {
        EXPECT_TRUE(wm::IsActiveWindow(unsnappable_window.get()));
        EXPECT_EQ(WindowStateType::kMaximized,
                  unsnappable_window_state->GetStateType());
        EXPECT_FALSE(split_view_controller()->InSplitViewMode());
        EXPECT_FALSE(InOverviewSession());
      };
  expect_unsnappable_window_is_active_and_maximized();
  const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY);
  unsnappable_window_state->OnWMEvent(&alt_left_square_bracket);
  expect_unsnappable_window_is_active_and_maximized();
  const WindowSnapWMEvent alt_right_square_bracket(
      WM_EVENT_CYCLE_SNAP_SECONDARY);
  unsnappable_window_state->OnWMEvent(&alt_right_square_bracket);
  expect_unsnappable_window_is_active_and_maximized();
}

// Tests using Alt+[ on a left snapped window, and Alt+] on a right snapped
// window.
TEST_F(SplitViewOverviewSessionTest, AltSquareBracketOnSameSideSnappedWindow) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  const auto test_unsnapping_window1 = [this,
                                        &window1](WMEventType event_type) {
    wm::ActivateWindow(window1.get());
    WindowState* window1_state = WindowState::Get(window1.get());
    const WindowSnapWMEvent event(event_type);
    window1_state->OnWMEvent(&event);
    EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
    EXPECT_EQ(WindowStateType::kMaximized, window1_state->GetStateType());
    EXPECT_FALSE(split_view_controller()->InSplitViewMode());
    EXPECT_FALSE(InOverviewSession());
  };
  // Test Alt+[ with active window snapped on left and overview on right.
  ToggleOverview();
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_PRIMARY);
  // Test Alt+] with active window snapped on right and overview on left.
  ToggleOverview();
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kSecondary);
  test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_SECONDARY);
  // Test Alt+[ with active window snapped on left and other window snapped on
  // right, if the left window is the default snapped window.
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
  test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_PRIMARY);
  // Test Alt+[ with active window snapped on left and other window snapped on
  // right, if the right window is the default snapped window.
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_PRIMARY);
  // Test Alt+] with active window snapped on right and other window snapped on
  // left, if the left window is the default snapped window.
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kPrimary);
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kSecondary);
  test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_SECONDARY);
  // Test Alt+] with active window snapped on right and other window snapped on
  // left, if the right window is the default snapped window.
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kSecondary);
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kPrimary);
  test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_SECONDARY);
}

// Tests using Alt+[ on a right snapped window, and Alt+] on a left snapped
// window.
TEST_F(SplitViewOverviewSessionTest,
       AltSquareBracketOnOppositeSideSnappedWindow) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  const auto test_left_snapping_window1 = [this, &window1, &window2]() {
    wm::ActivateWindow(window1.get());
    WindowState* window1_state = WindowState::Get(window1.get());
    const WindowSnapWMEvent alt_left_square_bracket(
        WM_EVENT_CYCLE_SNAP_PRIMARY);
    window1_state->OnWMEvent(&alt_left_square_bracket);
    EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
    EXPECT_EQ(WindowStateType::kPrimarySnapped, window1_state->GetStateType());
    EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
              split_view_controller()->state());
    EXPECT_EQ(window1.get(), split_view_controller()->primary_window());
    ASSERT_TRUE(InOverviewSession());
    EXPECT_TRUE(GetOverviewItemForWindow(window2.get()));
  };
  const auto test_right_snapping_window1 = [this, &window1, &window2]() {
    wm::ActivateWindow(window1.get());
    WindowState* window1_state = WindowState::Get(window1.get());
    const WindowSnapWMEvent alt_right_square_bracket(
        WM_EVENT_CYCLE_SNAP_SECONDARY);
    window1_state->OnWMEvent(&alt_right_square_bracket);
    EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
    EXPECT_EQ(WindowStateType::kSecondarySnapped,
              window1_state->GetStateType());
    EXPECT_EQ(SplitViewController::State::kSecondarySnapped,
              split_view_controller()->state());
    EXPECT_EQ(window1.get(), split_view_controller()->secondary_window());
    ASSERT_TRUE(InOverviewSession());
    EXPECT_TRUE(GetOverviewItemForWindow(window2.get()));
  };
  // Test Alt+[ with active window snapped on right and overview on left.
  ToggleOverview();
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kSecondary);
  test_left_snapping_window1();
  // Test Alt+] with active window snapped on left and overview on right.
  test_right_snapping_window1();
  // Test Alt+[ with active window snapped on right and other window snapped on
  // left, if the right window is the default snapped window.
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kPrimary);
  test_left_snapping_window1();
  // Test Alt+] with active window snapped on left and other window snapped on
  // right, if the left window is the default snapped window.
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
  test_right_snapping_window1();
  // Test Alt+[ with active window snapped on right and other window snapped on
  // left, if the left window is the default snapped window.
  EndSplitView();
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kPrimary);
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kSecondary);
  test_left_snapping_window1();
  // Test Alt+] with active window snapped on left and other window snapped on
  // right, if the right window is the default snapped window.
  EndSplitView();
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  test_right_snapping_window1();
}

// Test the overview window drag functionalities when screen rotates.
TEST_F(SplitViewOverviewSessionTest, SplitViewRotationTest) {
  UpdateDisplay("807x407");
  int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  display::test::ScopedSetInternalDisplayId set_internal(display_manager,
                                                         display_id);
  ScreenOrientationControllerTestApi test_api(
      Shell::Get()->screen_orientation_controller());

  // Set the screen orientation to LANDSCAPE_PRIMARY.
  test_api.SetDisplayRotation(display::Display::ROTATE_0,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(test_api.GetCurrentOrientation(),
            chromeos::OrientationType::kLandscapePrimary);

  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));

  ToggleOverview();
  // Test that dragging |window1| to the left of the screen snaps it to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window1.get());

  // Test that dragging |window2| to the right of the screen snaps it to right.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  gfx::Rect work_area_rect = GetWorkAreaInScreen(window2.get());
  gfx::PointF end_location2(work_area_rect.width(), work_area_rect.height());
  DragWindowTo(overview_item2, end_location2);
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  EXPECT_EQ(split_view_controller()->secondary_window(), window2.get());

  // Test that |left_window_| was snapped to left after rotated 0 degree.
  gfx::Rect left_window_bounds =
      split_view_controller()->primary_window()->GetBoundsInScreen();
  EXPECT_EQ(left_window_bounds.x(), work_area_rect.x());
  EXPECT_EQ(left_window_bounds.y(), work_area_rect.y());
  EndSplitView();

  // Rotate the screen by 270 degree.
  test_api.SetDisplayRotation(display::Display::ROTATE_270,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(test_api.GetCurrentOrientation(),
            chromeos::OrientationType::kPortraitPrimary);
  ToggleOverview();

  // Test that dragging |window1| to the top of the screen snaps it to left.
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window1.get());

  // Test that dragging |window2| to the bottom of the screen snaps it to right.
  overview_item2 = GetOverviewItemForWindow(window2.get());
  work_area_rect = GetWorkAreaInScreen(window2.get());
  end_location2 = gfx::PointF(work_area_rect.width(), work_area_rect.height());
  DragWindowTo(overview_item2, end_location2, SelectorItemLocation::ORIGIN);
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  EXPECT_EQ(split_view_controller()->secondary_window(), window2.get());

  // Test that |left_window_| was snapped to top after rotated 270 degree.
  left_window_bounds =
      split_view_controller()->primary_window()->GetBoundsInScreen();
  EXPECT_EQ(left_window_bounds.x(), work_area_rect.x());
  EXPECT_EQ(left_window_bounds.y(), work_area_rect.y());
  EndSplitView();

  // Rotate the screen by 180 degree.
  test_api.SetDisplayRotation(display::Display::ROTATE_180,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(test_api.GetCurrentOrientation(),
            chromeos::OrientationType::kLandscapeSecondary);
  ToggleOverview();

  // Test that dragging |window1| to the left of the screen snaps it to right.
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kSecondarySnapped);
  EXPECT_EQ(split_view_controller()->secondary_window(), window1.get());

  // Test that dragging |window2| to the right of the screen snaps it to left.
  overview_item2 = GetOverviewItemForWindow(window2.get());
  work_area_rect = GetWorkAreaInScreen(window2.get());
  end_location2 = gfx::PointF(work_area_rect.width(), work_area_rect.height());
  DragWindowTo(overview_item2, end_location2, SelectorItemLocation::ORIGIN);
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window2.get());

  // Test that |right_window_| was snapped to left after rotated 180 degree.
  gfx::Rect right_window_bounds =
      split_view_controller()->secondary_window()->GetBoundsInScreen();
  EXPECT_EQ(right_window_bounds.x(), work_area_rect.x());
  EXPECT_EQ(right_window_bounds.y(), work_area_rect.y());
  EndSplitView();

  // Rotate the screen by 90 degree.
  test_api.SetDisplayRotation(display::Display::ROTATE_90,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(test_api.GetCurrentOrientation(),
            chromeos::OrientationType::kPortraitSecondary);
  ToggleOverview();

  // Test that dragging |window1| to the top of the screen snaps it to right.
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kSecondarySnapped);
  EXPECT_EQ(split_view_controller()->secondary_window(), window1.get());

  // Test that dragging |window2| to the bottom of the screen snaps it to left.
  overview_item2 = GetOverviewItemForWindow(window2.get());
  work_area_rect = GetWorkAreaInScreen(window2.get());
  end_location2 = gfx::PointF(work_area_rect.width(), work_area_rect.height());
  DragWindowTo(overview_item2, end_location2);
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window2.get());

  // Test that |right_window_| was snapped to top after rotated 90 degree.
  right_window_bounds =
      split_view_controller()->secondary_window()->GetBoundsInScreen();
  EXPECT_EQ(right_window_bounds.x(), work_area_rect.x());
  EXPECT_EQ(right_window_bounds.y(), work_area_rect.y());
  EndSplitView();
}

// Test that when split view mode and overview mode are both active at the same
// time, dragging the split view divider resizes the bounds of snapped window
// and the bounds of overview window grids at the same time.
TEST_F(SplitViewOverviewSessionTest, SplitViewOverviewBothActiveTest) {
  UpdateDisplay("907x407");

  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));

  ToggleOverview();

  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());

  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  const gfx::Rect window1_bounds = window1->GetBoundsInScreen();
  const gfx::Rect overview_grid_bounds = GetGridBounds();
  const gfx::Rect divider_bounds =
      GetSplitViewDividerBounds(false /* is_dragging */);

  // Test that window1, divider, overview grid are aligned horizontally.
  EXPECT_EQ(window1_bounds.right(), divider_bounds.x());
  EXPECT_EQ(divider_bounds.right(), overview_grid_bounds.x());

  const gfx::Point resize_start_location(divider_bounds.CenterPoint());
  split_view_divider()->StartResizeWithDivider(resize_start_location);
  const gfx::Point resize_end_location(300, 0);
  split_view_divider()->EndResizeWithDivider(resize_end_location);
  SkipDividerSnapAnimation();

  const gfx::Rect window1_bounds_after_resize = window1->GetBoundsInScreen();
  const gfx::Rect overview_grid_bounds_after_resize = GetGridBounds();
  const gfx::Rect divider_bounds_after_resize =
      GetSplitViewDividerBounds(false /* is_dragging */);

  // Test that window1, divider, overview grid are still aligned horizontally
  // after resizing.
  EXPECT_EQ(window1_bounds.right(), divider_bounds.x());
  EXPECT_EQ(divider_bounds.right(), overview_grid_bounds.x());

  // Test that window1, divider, overview grid's bounds are changed after
  // resizing.
  EXPECT_NE(window1_bounds, window1_bounds_after_resize);
  EXPECT_NE(overview_grid_bounds, overview_grid_bounds_after_resize);
  EXPECT_NE(divider_bounds, divider_bounds_after_resize);
}

// Verify that selecting an unsnappable window while in split view works as
// intended.
TEST_F(SplitViewOverviewSessionTest, SelectUnsnappableWindowInSplitView) {
  // Create one snappable and one unsnappable window.
  std::unique_ptr<aura::Window> window = CreateTestWindow();
  std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  // Snap the snappable window to enter split view mode.
  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  ASSERT_TRUE(split_view_controller()->InSplitViewMode());

  // Select the unsnappable window.
  auto* overview_item = GetOverviewItemForWindow(unsnappable_window.get());
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint()));
  generator->ClickLeftButton();

  // Verify that we are out of split view and overview mode, and that the active
  // window is the unsnappable window.
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(unsnappable_window.get(), window_util::GetActiveWindow());

  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  ToggleOverview();
  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);

  // Split view mode should be active. Overview mode should be ended.
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller()->state());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());

  ToggleOverview();
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());

  // Now select the unsnappable window.
  overview_item = GetOverviewItemForWindow(unsnappable_window.get());
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint()));
  generator->ClickLeftButton();

  // Split view mode should be ended. And the unsnappable window should be the
  // active window now.
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(unsnappable_window.get(), window_util::GetActiveWindow());
}

// Verify that when in overview mode, the selector items unsnappable indicator
// shows up when expected.
TEST_F(SplitViewOverviewSessionTest, OverviewUnsnappableIndicatorVisibility) {
  // Create three windows; two normal and one unsnappable, so that when after
  // snapping |window1| to enter split view we can test the state of each normal
  // and unsnappable windows.
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> window2(CreateTestWindow());
  std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  auto* snappable_overview_item = GetOverviewItemForWindow(window2.get());
  auto* unsnappable_overview_item =
      GetOverviewItemForWindow(unsnappable_window.get());

  // Note: |cannot_snap_label_view_| and its parent will be created on demand.
  EXPECT_FALSE(GetCannotSnapWidget(snappable_overview_item));
  ASSERT_FALSE(GetCannotSnapWidget(unsnappable_overview_item));

  // Snap the extra snappable window to enter split view mode.
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  ASSERT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(GetCannotSnapWidget(snappable_overview_item));
  views::Widget* cannot_snap_widget =
      GetCannotSnapWidget(unsnappable_overview_item);
  ASSERT_TRUE(cannot_snap_widget);
  EXPECT_EQ(1.f, cannot_snap_widget->GetLayer()->opacity());

  // Exiting the splitview will hide the unsnappable label.
  const gfx::Rect divider_bounds =
      GetSplitViewDividerBounds(/*is_dragging=*/false);
  GetEventGenerator()->set_current_screen_location(
      divider_bounds.CenterPoint());
  GetEventGenerator()->DragMouseTo(0, 0);
  SkipDividerSnapAnimation();

  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(0.f, cannot_snap_widget->GetLayer()->opacity());
}

// Verify that during "normal" dragging from overview (not drag-to-close), the
// dragged item's unsnappable indicator is temporarily suppressed.
TEST_F(SplitViewOverviewSessionTest,
       OverviewUnsnappableIndicatorVisibilityWhileDragging) {
  ui::GestureConfiguration* gesture_config =
      ui::GestureConfiguration::GetInstance();
  gesture_config->set_long_press_time_in_ms(1);
  gesture_config->set_short_press_time(base::Milliseconds(1));
  gesture_config->set_show_press_delay_in_ms(1);

  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();
  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  ASSERT_TRUE(split_view_controller()->InSplitViewMode());
  auto* unsnappable_overview_item =
      GetOverviewItemForWindow(unsnappable_window.get());
  views::Widget* cannot_snap_widget =
      GetCannotSnapWidget(unsnappable_overview_item);
  ASSERT_TRUE(cannot_snap_widget);
  ui::Layer* unsnappable_layer = cannot_snap_widget->GetLayer();
  ASSERT_EQ(1.f, unsnappable_layer->opacity());

  // Test that the unsnappable label is temporarily suppressed during mouse
  // dragging.
  ui::test::EventGenerator* generator = GetEventGenerator();
  const gfx::Point drag_starting_point = gfx::ToRoundedPoint(
      unsnappable_overview_item->target_bounds().CenterPoint());
  generator->set_current_screen_location(drag_starting_point);
  generator->PressLeftButton();
  using DragBehavior = OverviewWindowDragController::DragBehavior;
  EXPECT_EQ(DragBehavior::kUndefined,
            GetOverviewSession()
                ->window_drag_controller()
                ->current_drag_behavior_for_testing());
  EXPECT_EQ(1.f, unsnappable_layer->opacity());
  generator->MoveMouseBy(0, 20);
  EXPECT_EQ(DragBehavior::kNormalDrag,
            GetOverviewSession()
                ->window_drag_controller()
                ->current_drag_behavior_for_testing());
  EXPECT_EQ(0.f, unsnappable_layer->opacity());
  generator->ReleaseLeftButton();
  EXPECT_EQ(1.f, unsnappable_layer->opacity());

  // Test that the unsnappable label is temporarily suppressed during "normal"
  // touch dragging (not drag-to-close).
  generator->set_current_screen_location(drag_starting_point);
  generator->PressTouch();
  {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2));
    run_loop.Run();
  }
  EXPECT_EQ(DragBehavior::kNormalDrag,
            GetOverviewSession()
                ->window_drag_controller()
                ->current_drag_behavior_for_testing());
  EXPECT_EQ(0.f, unsnappable_layer->opacity());
  generator->MoveTouchBy(20, 0);
  generator->ReleaseTouch();
  EXPECT_EQ(1.f, unsnappable_layer->opacity());

  // Test that the unsnappable label reappears if "normal" touch dragging (not
  // drag-to-close) ends when the item has not been actually dragged anywhere.
  // This case improves test coverage because it is handled in
  // |OverviewWindowDragController::ResetGesture| instead of
  // |OverviewWindowDragController::CompleteNormalDrag|.
  generator->set_current_screen_location(drag_starting_point);
  generator->PressTouch();
  {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2));
    run_loop.Run();
  }
  EXPECT_EQ(DragBehavior::kNormalDrag,
            GetOverviewSession()
                ->window_drag_controller()
                ->current_drag_behavior_for_testing());
  EXPECT_EQ(0.f, unsnappable_layer->opacity());
  generator->ReleaseTouch();
  EXPECT_EQ(1.f, unsnappable_layer->opacity());

  // Test that the unsnappable label persists in drag-to-close mode.
  generator->set_current_screen_location(drag_starting_point);
  generator->PressTouch();
  // Use small increments otherwise a fling event will be fired.
  for (int j = 0; j < 20; ++j)
    generator->MoveTouchBy(0, 1);
  EXPECT_EQ(DragBehavior::kDragToClose,
            GetOverviewSession()
                ->window_drag_controller()
                ->current_drag_behavior_for_testing());
  // Drag-to-close mode affects the opacity of the whole overview item,
  // including the unsnappable label.
  EXPECT_EQ(unsnappable_overview_item->GetWindow()->layer()->opacity(),
            unsnappable_layer->opacity());
  generator->ReleaseTouch();
  EXPECT_EQ(1.f, unsnappable_layer->opacity());
}

// Verify that an item's unsnappable indicator is updated for display rotation.
TEST_F(SplitViewOverviewSessionTest,
       OverviewUnsnappableIndicatorVisibilityAfterDisplayRotation) {
  UpdateDisplay("900x800");
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  // Because of its minimum size, |overview_window| is snappable in horizontal
  // split view but not in vertical split view.
  std::unique_ptr<aura::Window> overview_window(
      CreateWindowWithMinimumSize(gfx::Rect(400, 600), gfx::Size(300, 500)));
  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  ASSERT_TRUE(split_view_controller()->InSplitViewMode());
  auto* overview_item = GetOverviewItemForWindow(overview_window.get());
  // Note: |cannot_snap_label_view_| and its parent will be created on demand.
  EXPECT_FALSE(GetCannotSnapWidget(overview_item));

  // Rotate to primary portrait orientation. The unsnappable indicator appears.
  display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
      .SetFirstDisplayAsInternalDisplay();
  ScreenOrientationControllerTestApi test_api(
      Shell::Get()->screen_orientation_controller());
  test_api.SetDisplayRotation(display::Display::ROTATE_270,
                              display::Display::RotationSource::ACTIVE);
  views::Widget* cannot_snap_widget = GetCannotSnapWidget(overview_item);
  ASSERT_TRUE(cannot_snap_widget);
  ui::Layer* unsnappable_layer = cannot_snap_widget->GetLayer();
  EXPECT_EQ(1.f, unsnappable_layer->opacity());

  // Rotate to primary landscape orientation. The unsnappable indicator hides.
  test_api.SetDisplayRotation(display::Display::ROTATE_0,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(0.f, unsnappable_layer->opacity());
}

// Test that when splitview mode and overview mode are both active at the same
// time, dragging divider behaviors are correct.
TEST_F(SplitViewOverviewSessionTest, DragDividerToExitTest) {
  UpdateDisplay("907x407");

  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));

  ToggleOverview();

  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  // Test that overview mode and split view mode are both active.
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());

  // Drag the divider toward closing the snapped window.
  gfx::Rect divider_bounds = GetSplitViewDividerBounds(false /* is_dragging */);
  split_view_divider()->StartResizeWithDivider(divider_bounds.CenterPoint());
  split_view_divider()->EndResizeWithDivider(gfx::Point(0, 0));
  SkipDividerSnapAnimation();

  // Test that split view mode is ended. Overview mode is still active.
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());

  // Now drag |window2| selector item to snap to left.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  DragWindowTo(overview_item2, gfx::PointF());
  // Test that overview mode and split view mode are both active.
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());

  // Drag the divider toward closing the overview window grid.
  divider_bounds = GetSplitViewDividerBounds(false /*is_dragging=*/);
  const gfx::Rect display_bounds = GetWorkAreaInScreen(window2.get());
  split_view_divider()->StartResizeWithDivider(divider_bounds.CenterPoint());
  split_view_divider()->EndResizeWithDivider(display_bounds.bottom_right());
  SkipDividerSnapAnimation();

  // Test that split view mode is ended. Overview mode is also ended. |window2|
  // should be activated.
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
  EXPECT_EQ(window2.get(), window_util::GetActiveWindow());
}

TEST_F(SplitViewOverviewSessionTest, OverviewItemLongPressed) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  auto* overview_item = GetOverviewItemForWindow(window1.get());
  gfx::PointF start_location(overview_item->target_bounds().CenterPoint());
  const gfx::RectF original_bounds(overview_item->target_bounds());

  // Verify that when a overview item receives a resetting gesture, we
  // stay in overview mode and the bounds of the item are the same as they were
  // before the press sequence started.
  GetOverviewSession()->InitiateDrag(overview_item, start_location,
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/overview_item);
  GetOverviewSession()->ResetDraggedWindowGesture();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(original_bounds, overview_item->target_bounds());

  // Verify that when a overview item is tapped, we exit overview mode,
  // and the current active window is the item.
  GetOverviewSession()->InitiateDrag(overview_item, start_location,
                                     /*is_touch_dragging=*/true,
                                     /*event_source_item=*/overview_item);
  GetOverviewSession()->ActivateDraggedWindow();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(window1.get(), window_util::GetActiveWindow());
}

TEST_F(SplitViewOverviewSessionTest, SnappedWindowBoundsTest) {
  const gfx::Rect bounds(400, 400);
  const int kMinimumBoundSize = 100;
  const gfx::Size size(kMinimumBoundSize, kMinimumBoundSize);

  std::unique_ptr<aura::Window> window1(
      CreateWindowWithMinimumSize(bounds, size));
  std::unique_ptr<aura::Window> window2(
      CreateWindowWithMinimumSize(bounds, size));
  std::unique_ptr<aura::Window> window3(
      CreateWindowWithMinimumSize(bounds, size));
  const int screen_width =
      screen_util::GetDisplayWorkAreaBoundsInParent(window1.get()).width();
  ToggleOverview();

  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());

  // Then drag the divider to left toward closing the snapped window.
  gfx::Rect divider_bounds = GetSplitViewDividerBounds(false /*is_dragging=*/);
  split_view_divider()->StartResizeWithDivider(divider_bounds.CenterPoint());
  // Drag the divider to a point that is close enough but still have a short
  // distance to the edge of the screen.
  split_view_divider()->EndResizeWithDivider(gfx::Point(20, 20));
  SkipDividerSnapAnimation();

  // Test that split view mode is ended. Overview mode is still active.
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  // Test that |window1| has the dimensions of a tablet mode maxed window, so
  // that when it is placed back on the grid it will not look skinny.
  EXPECT_LE(window1->bounds().x(), 0);
  EXPECT_EQ(window1->bounds().width(), screen_width);

  // Drag |window2| selector item to snap to right.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  const gfx::Rect work_area_rect = GetWorkAreaInScreen(window2.get());
  gfx::Point end_location2 =
      gfx::Point(work_area_rect.width(), work_area_rect.height());
  DragWindowTo(overview_item2, gfx::PointF(end_location2));
  EXPECT_EQ(SplitViewController::State::kSecondarySnapped,
            split_view_controller()->state());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());

  // Then drag the divider to right toward closing the snapped window.
  divider_bounds = GetSplitViewDividerBounds(false /* is_dragging */);
  split_view_divider()->StartResizeWithDivider(divider_bounds.CenterPoint());
  // Drag the divider to a point that is close enough but still have a short
  // distance to the edge of the screen.
  end_location2.Offset(-20, -20);
  split_view_divider()->EndResizeWithDivider(end_location2);
  SkipDividerSnapAnimation();

  // Test that split view mode is ended. Overview mode is still active.
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  // Test that |window2| has the dimensions of a tablet mode maxed window, so
  // that when it is placed back on the grid it will not look skinny.
  EXPECT_GE(window2->bounds().x(), 0);
  EXPECT_EQ(window2->bounds().width(), screen_width);
}

TEST_F(SplitViewOverviewSessionTest, ResizePastFixedDividerPositions) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));

  // Start overview and drag to snap `window1` in split view.
  ToggleOverview();
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  const gfx::Point start_point(window1->GetBoundsInScreen().right_center());
  auto* generator = GetEventGenerator();
  generator->set_current_screen_location(start_point);

  // Resize the window to less than 1/3 of the work area.
  const int work_area_length(GetWorkAreaInScreen(window1.get()).width());
  int window_length = 200;
  ASSERT_LT(window_length, work_area_length * chromeos::kOneThirdSnapRatio);
  split_view_divider()->StartResizeWithDivider(
      GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint());
  split_view_divider()->EndResizeWithDivider(
      gfx::Point(window_length, start_point.y()));

  // We remain in overview and the divider will be animated to 1/2.
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  SkipDividerSnapAnimation();
  EXPECT_NEAR(
      GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint().x(),
      work_area_length * chromeos::kOneThirdSnapRatio, 1.f);

  // Resize the window to greater than 2/3 of the work area.
  window_length = 600;
  ASSERT_GT(window_length, work_area_length * chromeos::kTwoThirdSnapRatio);
  split_view_divider()->StartResizeWithDivider(
      GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint());
  split_view_divider()->EndResizeWithDivider(
      gfx::Point(window_length, start_point.y()));

  // We remain in overview and the divider will be animated to 1/2.
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  SkipDividerSnapAnimation();
  EXPECT_NEAR(
      GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint().x(),
      work_area_length * chromeos::kTwoThirdSnapRatio, 1.f);
}

// Test snapped window bounds with adjustment for the minimum size of a window.
TEST_F(SplitViewOverviewSessionTest, SnappedWindowBoundsWithMinimumSizeTest) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
  const gfx::Rect work_area =
      screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
          window1.get());
  std::unique_ptr<aura::Window> window2(CreateWindowWithMinimumSize(
      bounds, gfx::Size(work_area.width() / 3 + 20, 0)));

  // Snap `window1` in split view, then resize it to 1/3 the work area, which is
  // less than the minimum size of `window2`.
  ToggleOverview();
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_divider()->StartResizeWithDivider(
      GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint());
  split_view_divider()->EndResizeWithDivider(
      gfx::Point(work_area.width() / 3, 10));
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  ASSERT_TRUE(split_view_controller()->InSplitViewMode());
  // Use `EXPECT_NEAR` for reasons related to rounding and divider thickness.
  constexpr int kDividerWidth = kSplitviewDividerShortSideLength;
  ASSERT_NEAR(work_area.width() / 3, window1->GetBoundsInScreen().width(),
              kDividerWidth);

  // Long press to start a drag on `item2` to the left, on top of `window1`, to
  // show the left highlight preview.
  auto* item2 = GetOverviewItemForWindow(window2.get());
  const gfx::Point drag_starting_point(
      gfx::ToRoundedPoint(item2->GetTransformedBounds().CenterPoint()));
  ui::test::EventGenerator* generator = GetEventGenerator();
  LongGestureTap(drag_starting_point, generator, /*release_touch=*/false);
  DragItemToPoint(item2, gfx::Point(0, 0), generator,
                  /*by_touch_gestures=*/true, /*drop=*/false);
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  ASSERT_TRUE(split_view_controller()->InSplitViewMode());

  // Test that the highlight bounds are 1/2 the work area, since that's the
  // closest fixed divider ratio for `window2`.
  gfx::Rect left_highlight_bounds(work_area);
  left_highlight_bounds.set_width(work_area.width() / 2 - kDividerWidth / 2);
  left_highlight_bounds.Inset(kHighlightScreenEdgePaddingDp);
  auto* overview_grid =
      GetOverviewSession()->GetGridWithRootWindow(window1->GetRootWindow());
  EXPECT_EQ(left_highlight_bounds, overview_grid->split_view_drag_indicators()
                                       ->GetLeftHighlightViewBounds());

  // Drop `item2` back at its starting point.
  generator->MoveTouch(drag_starting_point);
  generator->ReleaseTouch();

  // Now resize `window1` where `window2` can't fit in the secondary position.
  split_view_divider()->StartResizeWithDivider(
      GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint());
  split_view_divider()->EndResizeWithDivider(
      gfx::Point(work_area.width() * 2 / 3, 10));
  ASSERT_NEAR(work_area.width() * 2 / 3, window1->GetBoundsInScreen().width(),
              kDividerWidth);

  // Drag `window2` to show the right highlight preview.
  DragItemToPoint(item2, work_area.top_right(), generator,
                  /*by_touch_gestures=*/false, /*drop=*/false);

  // Test that the highlight bounds are 1/2 the work area.
  gfx::Rect right_highlight_bounds(work_area);
  right_highlight_bounds.set_x(work_area.width() / 2 + kDividerWidth / 2);
  right_highlight_bounds.set_width(work_area.width() / 2 - kDividerWidth / 2);
  right_highlight_bounds.Inset(kHighlightScreenEdgePaddingDp);
  EXPECT_EQ(right_highlight_bounds,
            overview_grid->split_view_drag_indicators()
                ->GetRightHighlightViewBoundsForTesting());
  generator->ReleaseTouch();
}

// Verify that if the split view divider is dragged all the way to the edge, the
// window being dragged gets returned to the overview list, if overview mode is
// still active.
TEST_F(SplitViewOverviewSessionTest,
       DividerDraggedToEdgeReturnsWindowToOverviewList) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));

  ToggleOverview();
  // Drag |window1| selector item to snap to left. There should be two items on
  // the overview grid afterwards, |window2| and |window3|.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  ASSERT_TRUE(split_view_controller()->split_view_divider()->divider_widget());
  const std::vector<aura::Window*> window_list =
      GetWindowsListInOverviewGrids();
  EXPECT_EQ(2u, window_list.size());
  EXPECT_FALSE(base::Contains(window_list, window1.get()));
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));

  // Drag the divider to the left edge.
  const gfx::Rect divider_bounds =
      GetSplitViewDividerBounds(/*is_dragging=*/false);
  GetEventGenerator()->set_current_screen_location(
      divider_bounds.CenterPoint());
  GetEventGenerator()->DragMouseTo(0, 0);
  SkipDividerSnapAnimation();

  // Verify that it is still in overview mode and that |window1| is returned to
  // the overview list.
  EXPECT_TRUE(InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  const std::vector<aura::Window*> new_window_list =
      GetWindowsListInOverviewGrids();
  EXPECT_EQ(3u, new_window_list.size());
  EXPECT_TRUE(base::Contains(new_window_list, window1.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
}

// Verify that if overview mode is active and the split view divider is dragged
// all the way to the opposite edge, then the split view window is reinserted
// into the overview grid at the correct position according to MRU order, and
// the stacking order is also correct.
TEST_F(
    SplitViewOverviewSessionTest,
    SplitViewWindowReinsertedToOverviewAtCorrectPositionWhenSplitViewIsEnded) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
  ToggleOverview();
  DragWindowTo(GetOverviewItemForWindow(window1.get()), gfx::PointF());
  DragWindowTo(GetOverviewItemForWindow(window2.get()),
               gfx::PointF(799.f, 0.f));
  EXPECT_EQ(window1.get(), split_view_controller()->primary_window());
  EXPECT_EQ(window2.get(), split_view_controller()->secondary_window());
  ToggleOverview();
  // Drag the divider to the left edge.
  const gfx::Rect divider_bounds =
      GetSplitViewDividerBounds(/*is_dragging=*/false);
  GetEventGenerator()->set_current_screen_location(
      divider_bounds.CenterPoint());
  GetEventGenerator()->DragMouseTo(0, 0);
  SkipDividerSnapAnimation();

  // Verify the grid arrangement.
  ASSERT_TRUE(InOverviewSession());
  const std::vector<raw_ptr<aura::Window, VectorExperimental>>
      expected_mru_list = {window2.get(), window1.get(), window3.get()};
  const std::vector<aura::Window*> expected_overview_list = {
      window2.get(), window1.get(), window3.get()};
  EXPECT_EQ(
      expected_mru_list,
      Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk));
  EXPECT_EQ(expected_overview_list, GetWindowsListInOverviewGrids());

  // Verify the stacking order.
  aura::Window* parent = window1->parent();
  ASSERT_EQ(parent, window2->parent());
  ASSERT_EQ(parent, window3->parent());
  EXPECT_TRUE(window_util::IsStackedBelow(
      GetOverviewItemForWindow(window1.get())->item_widget()->GetNativeWindow(),
      GetOverviewItemForWindow(window2.get())
          ->item_widget()
          ->GetNativeWindow()));
  EXPECT_TRUE(window_util::IsStackedBelow(
      GetOverviewItemForWindow(window3.get())->item_widget()->GetNativeWindow(),
      GetOverviewItemForWindow(window1.get())
          ->item_widget()
          ->GetNativeWindow()));
}

// Verify that if a window is dragged from overview and snapped in place of
// another split view window, then the old split view window is reinserted into
// the overview grid at the correct position according to MRU order, and the
// stacking order is also correct.
TEST_F(
    SplitViewOverviewSessionTest,
    SplitViewWindowReinsertedToOverviewAtCorrectPositionWhenAnotherWindowTakesItsPlace) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window4(CreateWindow(bounds));
  ToggleOverview();
  DragWindowTo(GetOverviewItemForWindow(window1.get()), gfx::PointF());
  DragWindowTo(GetOverviewItemForWindow(window2.get()),
               gfx::PointF(799.f, 0.f));
  EXPECT_EQ(window1.get(), split_view_controller()->primary_window());
  EXPECT_EQ(window2.get(), split_view_controller()->secondary_window());
  ToggleOverview();
  DragWindowTo(GetOverviewItemForWindow(window3.get()), gfx::PointF());
  EXPECT_EQ(window3.get(), split_view_controller()->primary_window());

  // Verify the grid arrangement.
  ASSERT_TRUE(InOverviewSession());
  const std::vector<raw_ptr<aura::Window, VectorExperimental>>
      expected_mru_list = {window3.get(), window2.get(), window1.get(),
                           window4.get()};
  const std::vector<aura::Window*> expected_overview_list = {
      window2.get(), window1.get(), window4.get()};
  EXPECT_EQ(
      expected_mru_list,
      Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk));
  EXPECT_EQ(expected_overview_list, GetWindowsListInOverviewGrids());

  // Verify the stacking order.
  aura::Window* parent = window1->parent();
  ASSERT_EQ(parent, window2->parent());
  ASSERT_EQ(parent, window4->parent());
  EXPECT_TRUE(window_util::IsStackedBelow(
      GetOverviewItemForWindow(window1.get())->item_widget()->GetNativeWindow(),
      GetOverviewItemForWindow(window2.get())
          ->item_widget()
          ->GetNativeWindow()));
  EXPECT_TRUE(window_util::IsStackedBelow(
      GetOverviewItemForWindow(window4.get())->item_widget()->GetNativeWindow(),
      GetOverviewItemForWindow(window1.get())
          ->item_widget()
          ->GetNativeWindow()));
}

// Verify that if the split view divider is dragged close to the edge, the grid
// bounds will be fixed to a third of the work area width and start sliding off
// the screen instead of continuing to shrink.
TEST_F(SplitViewOverviewSessionTest,
       OverviewHasMinimumBoundsWhenDividerDragged) {
  UpdateDisplay("600x400");

  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));

  ToggleOverview();
  // Snap a window to the left and test dragging the divider towards the right
  // edge of the screen.
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
  ASSERT_TRUE(grid);

  // Drag the divider to the right edge.
  gfx::Rect divider_bounds = GetSplitViewDividerBounds(/*is_dragging=*/false);
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(divider_bounds.CenterPoint());
  generator->PressLeftButton();

  // Tests that near the right edge, the grid bounds are fixed at 200 and are
  // partially off screen to the right. Drag with at least 2 steps to
  // simulate a real mouse drag movement.
  generator->MoveMouseTo(gfx::Point(580, 0), /*count=*/2);
  gfx::Rect grid_bounds = OverviewGridTestApi(grid).bounds();
  EXPECT_EQ(200, grid_bounds.width());
  EXPECT_GT(grid_bounds.right(), 600);
  generator->ReleaseLeftButton();
  SkipDividerSnapAnimation();

  // Releasing close to the edge will activate the left window and exit
  // overview.
  ASSERT_FALSE(InOverviewSession());
  ToggleOverview();
  // Snap a window to the right and test dragging the divider towards the left
  // edge of the screen.
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kSecondary);
  grid = GetOverviewSession()->grid_list()[0].get();
  ASSERT_TRUE(grid);

  // Drag the divider to the left edge.
  divider_bounds = GetSplitViewDividerBounds(/*is_dragging=*/false);
  generator->set_current_screen_location(divider_bounds.CenterPoint());
  generator->PressLeftButton();

  // Drag with at least 2 steps to simulate a real mouse drag movement.
  generator->MoveMouseTo(gfx::Point(20, 0), /*count=*/2);
  // Tests that near the left edge, the grid bounds are fixed at 200 and are
  // partially off screen to the left.
  grid_bounds = OverviewGridTestApi(grid).bounds();
  EXPECT_EQ(200, grid_bounds.width());
  EXPECT_LT(grid_bounds.x(), 0);
  generator->ReleaseLeftButton();
  SkipDividerSnapAnimation();
}

// Test that when splitview mode is active, minimizing one of the snapped window
// will insert the minimized window back to overview mode if overview mode is
// active at the moment.
TEST_F(SplitViewOverviewSessionTest, InsertMinimizedWindowBackToOverview) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));

  ToggleOverview();
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window1.get());
  EXPECT_TRUE(InOverviewSession());

  // Minimize |window1| will put |window1| back to overview grid.
  WindowState::Get(window1.get())->Minimize();
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(GetOverviewItemForWindow(window1.get()));

  // Now snap both |window1| and |window2|.
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  wm::ActivateWindow(window2.get());
  EXPECT_FALSE(InOverviewSession());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kBothSnapped);
  EXPECT_EQ(split_view_controller()->primary_window(), window1.get());
  EXPECT_EQ(split_view_controller()->secondary_window(), window2.get());

  // Minimize |window1| will open overview and put |window1| to overview grid.
  WindowState::Get(window1.get())->Minimize();
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kSecondarySnapped);
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(GetOverviewItemForWindow(window1.get()));

  // Minimize |window2| also put |window2| to overview grid.
  WindowState::Get(window2.get())->Minimize();
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(GetOverviewItemForWindow(window1.get()));
  EXPECT_TRUE(GetOverviewItemForWindow(window2.get()));
}

// Test that when splitview and overview are both active at the same time, if
// overview is ended due to snapping a window in splitview, the tranform of each
// window in the overview grid is restored.
TEST_F(SplitViewOverviewSessionTest, SnappedWindowAnimationObserverTest) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));

  // There are four ways to exit overview mode. Verify in each case the
  // tranform of each window in the overview window grid has been restored.

  // 1. Overview is ended by dragging a item in overview to snap to splitview.
  // Drag |window1| selector item to snap to left. There should be two items on
  // the overview grid afterwards, |window2| and |window3|.
  ToggleOverview();
  EXPECT_FALSE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity());
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  // Drag |window2| to snap to right.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  const gfx::Rect work_area_rect =
      screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
          window2.get());
  const gfx::PointF end_location2(work_area_rect.width(), 0);
  DragWindowTo(overview_item2, end_location2);
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller()->state());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity());

  // 2. Overview is ended by ToggleOverview() directly.
  // ToggleOverview() will open overview grid in the non-default side of the
  // split screen.
  ToggleOverview();
  EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  // ToggleOverview() directly.
  ToggleOverview();
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller()->state());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity());

  // 3. Overview is ended by actviating an existing window.
  ToggleOverview();
  EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  wm::ActivateWindow(window2.get());
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller()->state());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity());

  // 4. Overview is ended by activating a new window.
  ToggleOverview();
  EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  std::unique_ptr<aura::Window> window4(CreateWindow(bounds));
  wm::ActivateWindow(window4.get());
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller()->state());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity());
  EXPECT_TRUE(window4->layer()->GetTargetTransform().IsIdentity());
}

// Test that when split view and overview are both active at the same time,
// double tapping on the divider can swap the window's position with the
// overview window grid's postion.
TEST_F(SplitViewOverviewSessionTest, SwapWindowAndOverviewGrid) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateAppWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateAppWindow(bounds));

  ToggleOverview();
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF());
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kPrimarySnapped);
  EXPECT_EQ(split_view_controller()->default_snap_position(),
            SnapPosition::kPrimary);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  // Test that the grid bounds are approximately equal to the bounds of a
  // snapped window (minus hotseat insets on the grid).
  EXPECT_EQ(
      split_view_controller()->GetSnappedWindowBoundsInScreen(
          SnapPosition::kSecondary,
          /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
          /*account_for_divider_width=*/true),
      GetGridBounds());

  split_view_controller()->SwapWindows();
  EXPECT_EQ(split_view_controller()->state(),
            SplitViewController::State::kSecondarySnapped);
  EXPECT_EQ(split_view_controller()->default_snap_position(),
            SnapPosition::kSecondary);
  EXPECT_EQ(
      split_view_controller()->GetSnappedWindowBoundsInScreen(
          SnapPosition::kPrimary,
          /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
          /*account_for_divider_width=*/true),
      GetGridBounds());
}

// Test that in tablet mode, pressing tab key in overview should not crash.
TEST_F(SplitViewOverviewSessionTest, NoCrashWhenPressTabKey) {
  std::unique_ptr<aura::Window> window(CreateWindow(gfx::Rect(400, 400)));
  std::unique_ptr<aura::Window> window2(CreateWindow(gfx::Rect(400, 400)));

  // In overview, there should be no crash when pressing tab key.
  ToggleOverview();
  EXPECT_TRUE(InOverviewSession());
  PressAndReleaseKey(ui::VKEY_TAB);
  EXPECT_TRUE(InOverviewSession());

  // When splitview and overview are both active, there should be no crash when
  // pressing tab key.
  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());

  PressAndReleaseKey(ui::VKEY_TAB);
  EXPECT_TRUE(InOverviewSession());
}

// Tests closing a snapped window while in overview mode.
TEST_F(SplitViewOverviewSessionTest, ClosingSplitViewWindow) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));

  ToggleOverview();
  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());

  // Now close the snapped |window1|. We should remain in overview mode and the
  // overview focus window should regain focus.
  window1.reset();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(),
            window_util::GetFocusedWindow());
}

// Test that you cannot drag from overview during the split view divider
// animation.
TEST_F(SplitViewOverviewSessionTest,
       CannotDragFromOverviewDuringSplitViewDividerAnimation) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
  ToggleOverview();
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);

  gfx::Point divider_drag_point =
      split_view_controller()
          ->split_view_divider()
          ->GetDividerBoundsInScreen(/*is_dragging=*/false)
          .CenterPoint();
  split_view_divider()->StartResizeWithDivider(divider_drag_point);
  divider_drag_point.Offset(20, 0);
  split_view_divider()->ResizeWithDivider(divider_drag_point);
  split_view_divider()->EndResizeWithDivider(divider_drag_point);
  ASSERT_TRUE(IsDividerAnimating());

  auto* overview_item = GetOverviewItemForWindow(overview_window.get());
  GetOverviewSession()->InitiateDrag(
      overview_item, overview_item->target_bounds().CenterPoint(),
      /*is_touch_dragging=*/true, /*event_source_item=*/overview_item);
  EXPECT_FALSE(overview_item->IsDragItem());
}

// Tests that a window which is dragged to a splitview zone is destroyed, the
// grid bounds return to a non-splitview bounds.
TEST_F(SplitViewOverviewSessionTest, GridBoundsAfterWindowDestroyed) {
  // Create two windows otherwise we exit overview after one window is
  // destroyed.
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();

  ToggleOverview();
  const gfx::Rect grid_bounds = GetGridBounds();
  // Drag the item such that the splitview preview area shows up and the grid
  // bounds shrink.
  auto* overview_item = GetOverviewItemForWindow(window1.get());
  GetOverviewSession()->InitiateDrag(
      overview_item, overview_item->target_bounds().CenterPoint(),
      /*is_touch_dragging=*/true, /*event_source_item=*/overview_item);
  GetOverviewSession()->Drag(overview_item, gfx::PointF(1.f, 1.f));
  EXPECT_NE(grid_bounds, GetGridBounds());

  // Tests that when the dragged window is destroyed, the grid bounds return to
  // their normal size.
  window1.reset();
  EXPECT_EQ(grid_bounds, GetGridBounds());
}

// Tests that overview stays active if we have a snapped window.
TEST_F(SplitViewOverviewSessionTest, OnScreenLock) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();

  // Overview should exit if no snapped window after locking/unlocking.
  ToggleOverview();
  GetSessionControllerClient()->LockScreen();
  GetSessionControllerClient()->UnlockScreen();
  ASSERT_FALSE(InOverviewSession());

  ToggleOverview();
  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kPrimary);

  // Lock and unlock the machine. Test that we are still in overview and
  // splitview.
  GetSessionControllerClient()->LockScreen();
  GetSessionControllerClient()->UnlockScreen();
  EXPECT_TRUE(InOverviewSession());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
}

// Verify that selecting an minimized snappable window while in split view
// triggers auto snapping.
TEST_F(SplitViewOverviewSessionTest,
       SelectMinimizedSnappableWindowInSplitView) {
  // Create two snappable windows.
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> minimized_window = CreateTestWindow();
  WindowState::Get(minimized_window.get())->Minimize();

  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());

  // Snap a window to enter split view mode.
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());

  // Select the minimized window.
  auto* overview_item = GetOverviewItemForWindow(minimized_window.get());
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint()));
  generator->ClickLeftButton();

  // Verify that both windows are in a snapped state and overview mode is ended.
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(
      split_view_controller()->IsWindowInSplitView(snapped_window.get()));
  EXPECT_EQ(
      split_view_controller()->GetPositionOfSnappedWindow(snapped_window.get()),
      SnapPosition::kPrimary);
  EXPECT_TRUE(
      split_view_controller()->IsWindowInSplitView(minimized_window.get()));
  EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow(
                minimized_window.get()),
            SnapPosition::kSecondary);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_EQ(minimized_window.get(), window_util::GetActiveWindow());
}

// Verify no crash (or DCHECK failure) if you exit and re-enter mirror mode
// while in tablet split view with empty overview.
TEST_F(SplitViewOverviewSessionTest,
       ExitAndReenterMirrorModeWithEmptyOverview) {
  UpdateDisplay("800x600,800x600");
  std::unique_ptr<aura::Window> window = CreateTestWindow();
  ToggleOverview();
  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt);
  display_manager()->SetMirrorMode(display::MirrorMode::kNormal, std::nullopt);
}

// Tests that there is no crash when dragging the divider in portrait mode.
// Regression test for https://crbug.com/1267486.
TEST_F(SplitViewOverviewSessionTest, NoCrashWhenDraggingDividerInPortrait) {
  // The crash only occured in portrait mode.
  UpdateDisplay("600x800");
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();

  ToggleOverview();
  // Note that this snaps `window1` to the top.
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);

  // Drag the divider all the way to the bottom. There should be no crash.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      split_view_controller()
          ->split_view_divider()
          ->GetDividerBoundsInScreen(/*is_dragging=*/false)
          .CenterPoint());
  generator->PressTouch();
  generator->MoveTouchBy(0, 600);
  generator->ReleaseTouch();
}

// Tests that in tablet mode, after minimizing and unminimizng a snapped window,
// it is visible to the user. Regression test for b/267391123.
TEST_F(SplitViewOverviewSessionTest, WindowVisibleAfterMinimizeUnminimize) {
  std::unique_ptr<aura::Window> window = CreateAppWindow();
  auto* window_state = WindowState::Get(window.get());

  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  ASSERT_TRUE(InOverviewSession());
  ASSERT_FALSE(GetOverviewItemForWindow(window.get()));

  window_state->Minimize();
  ASSERT_TRUE(InOverviewSession());
  ASSERT_TRUE(GetOverviewItemForWindow(window.get()));

  window->Show();
  wm::ActivateWindow(window.get());
  EXPECT_TRUE(window_state->IsSnapped());
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(window->IsVisible());
  EXPECT_EQ(1.f, window->layer()->GetTargetOpacity());
}

// Tests the divider gains and loses activation in tablet mode.
TEST_F(SplitViewOverviewSessionTest, KeyboardFocus) {
  std::unique_ptr<aura::Window> window = CreateAppWindow();
  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  ASSERT_TRUE(InOverviewSession());

  auto* divider_widget =
      split_view_controller()->split_view_divider()->divider_widget();
  EXPECT_FALSE(divider_widget->IsActive());

  // Test the divider gains activation.
  while (!divider_widget->IsActive()) {
    PressAndReleaseKey(ui::VKEY_BROWSER_FORWARD, ui::EF_CONTROL_DOWN);
  }

  // Test the divider loses activation.
  PressAndReleaseKey(ui::VKEY_BROWSER_FORWARD, ui::EF_CONTROL_DOWN);
  EXPECT_FALSE(divider_widget->IsActive());
}

// Test the split view and overview functionalities in clamshell mode. Split
// view is only active when overview is active in clamshell mode.
class SplitViewOverviewSessionInClamshellTest
    : public SplitViewOverviewSessionTest {
 public:
  SplitViewOverviewSessionInClamshellTest() = default;

  SplitViewOverviewSessionInClamshellTest(
      const SplitViewOverviewSessionInClamshellTest&) = delete;
  SplitViewOverviewSessionInClamshellTest& operator=(
      const SplitViewOverviewSessionInClamshellTest&) = delete;

  ~SplitViewOverviewSessionInClamshellTest() override = default;

  // AshTestBase:
  void SetUp() override {
    SplitViewOverviewSessionTest::SetUp();
    Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
    DCHECK(ShouldAllowSplitView());
  }

  aura::Window* CreateWindowWithHitTestComponent(int hit_test_component,
                                                 const gfx::Rect& bounds) {
    return CreateTestWindowInShellWithDelegate(
        new TestWindowHitTestDelegate(hit_test_component), 0, bounds);
  }

 private:
  class TestWindowHitTestDelegate : public aura::test::TestWindowDelegate {
   public:
    explicit TestWindowHitTestDelegate(int hit_test_component) {
      set_window_component(hit_test_component);
    }

    TestWindowHitTestDelegate(const TestWindowHitTestDelegate&) = delete;
    TestWindowHitTestDelegate& operator=(const TestWindowHitTestDelegate&) =
        delete;

    ~TestWindowHitTestDelegate() override = default;

   private:
    // aura::Test::TestWindowDelegate:
    void OnWindowDestroyed(aura::Window* window) override { delete this; }
  };
};

// Test some basic functionalities in clamshell splitview mode.
TEST_F(SplitViewOverviewSessionInClamshellTest, BasicFunctionalitiesTest) {
  UpdateDisplay("600x400");
  EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());

  // 1. Test the 1 window scenario.
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateAppWindow(bounds));
  WindowState* window_state1 = WindowState::Get(window1.get());
  EXPECT_FALSE(window_state1->IsSnapped());
  ToggleOverview();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  // Since the only window is snapped, overview and splitview should be both
  // ended.
  EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kPrimarySnapped);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // 2. Test if one window is snapped, the other windows are showing in
  // overview, close all windows in overview will end overview and also
  // splitview.
  std::unique_ptr<aura::Window> window2(CreateAppWindow(bounds));
  ToggleOverview();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(600, 300));
  // SplitView and overview are both active at the moment.
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
  EXPECT_TRUE(GetOverviewController()->overview_session()->IsWindowInOverview(
      window2.get()));
  EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kSecondarySnapped);
  // Close |window2| will end overview and splitview.
  window2.reset();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // 3. Test that snap 2 windows will end overview and splitview.
  std::unique_ptr<aura::Window> window3(CreateAppWindow(bounds));
  ToggleOverview();
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  auto* overview_item3 = GetOverviewItemForWindow(window3.get());
  DragWindowTo(overview_item3, gfx::PointF(600, 300));
  EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kPrimarySnapped);
  EXPECT_EQ(WindowState::Get(window3.get())->GetStateType(),
            WindowStateType::kSecondarySnapped);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // Maximize `window3` as `window1` and `window3` may form a Snap Group with
  // `kSnapGroup` enabled.
  WindowState::Get(window3.get())->Maximize();

  // 4. Test if one window is snapped, the other windows are showing in
  // overview, we can drag another window in overview to snap in splitview, and
  // the previous snapped window will be put back into overview.
  std::unique_ptr<aura::Window> window4(CreateAppWindow(bounds));
  ToggleOverview();
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_FALSE(GetOverviewController()->overview_session()->IsWindowInOverview(
      window1.get()));
  overview_item3 = GetOverviewItemForWindow(window3.get());
  DragWindowTo(overview_item3, gfx::PointF(0, 0));
  EXPECT_FALSE(GetOverviewController()->overview_session()->IsWindowInOverview(
      window3.get()));
  EXPECT_TRUE(GetOverviewController()->overview_session()->IsWindowInOverview(
      window1.get()));
  EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kPrimarySnapped);
  EXPECT_EQ(WindowState::Get(window3.get())->GetStateType(),
            WindowStateType::kPrimarySnapped);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  // End overview, test that we'll not auto-snap a window to the right side of
  // the screen.
  EXPECT_EQ(WindowState::Get(window4.get())->GetStateType(),
            WindowStateType::kDefault);
  ToggleOverview();
  EXPECT_EQ(WindowState::Get(window4.get())->GetStateType(),
            WindowStateType::kDefault);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // 5. Test if one window is snapped, the other windows are showing in
  // overview, activating an new window will not auto-snap the new window.
  // Overview and splitview should be ended.
  ToggleOverview();
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  std::unique_ptr<aura::Window> window5(CreateWindow(bounds));
  EXPECT_EQ(WindowState::Get(window5.get())->GetStateType(),
            WindowStateType::kDefault);
  wm::ActivateWindow(window5.get());
  EXPECT_EQ(WindowState::Get(window5.get())->GetStateType(),
            WindowStateType::kDefault);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // 6. Test if one window is snapped, the other window is showing in overview,
  // close the snapped window will end split view, but overview is still active.
  ToggleOverview();
  const gfx::Rect overview_bounds = GetGridBounds();
  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_NE(GetGridBounds(), overview_bounds);
  EXPECT_EQ(GetGridBounds(), GetExpectedOverviewBounds());
  window1.reset();
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  // Overview bounds will adjust from snapped bounds to fullscreen bounds.
  EXPECT_EQ(GetGridBounds(), overview_bounds);

  // 7. Test if split view mode is active, open the app list will end both
  // overview and splitview.
  overview_item3 = GetOverviewItemForWindow(window3.get());
  DragWindowTo(overview_item3, gfx::PointF(0, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  // Open app list.
  AppListControllerImpl* app_list_controller =
      Shell::Get()->app_list_controller();
  app_list_controller->ToggleAppList(
      display::Screen::GetScreen()->GetDisplayNearestWindow(window3.get()).id(),
      AppListShowSource::kSearchKey, base::TimeTicks());
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // 8. Test if splitview is not active, open the app list will end overview if
  // overview is active.
  ToggleOverview();
  // Open app list.
  app_list_controller->ToggleAppList(
      display::Screen::GetScreen()->GetDisplayNearestWindow(window3.get()).id(),
      AppListShowSource::kSearchKey, base::TimeTicks());
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
}

TEST_F(SplitViewOverviewSessionInClamshellTest,
       ResizePastFixedDividerPositions) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds));
  std::unique_ptr<aura::Window> window2(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds));

  // Start overview and drag to snap `window1` in split view.
  ToggleOverview();
  DragWindowTo(GetOverviewItemForWindow(window1.get()), gfx::PointF(0, 0));
  EXPECT_TRUE(RootWindowController::ForWindow(window1.get())
                  ->split_view_overview_session());

  const gfx::Point start_point(window1->GetBoundsInScreen().right_center());
  auto* generator = GetEventGenerator();
  generator->set_current_screen_location(start_point);

  // Resize the window to less than 1/3 of the work area. Test we end overview.
  const int work_area_length(GetWorkAreaInScreen(window1.get()).width());
  int window_length = 200;
  ASSERT_LT(window_length, work_area_length * chromeos::kOneThirdSnapRatio);
  generator->DragMouseTo(gfx::Point(window_length, start_point.y()));
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());

  // Start overview and snap `window1` in split view again.
  ToggleOverview();
  DragWindowTo(GetOverviewItemForWindow(window1.get()), gfx::PointF(0, 0));

  // Resize the window to greater than 2/3 of the work area. Test we end
  // overview.
  window_length = 600;
  ASSERT_GT(window_length, work_area_length * chromeos::kTwoThirdSnapRatio);
  generator->set_current_screen_location(start_point);
  generator->DragMouseTo(gfx::Point(window_length, start_point.y()));
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
}

// Test overview exit animation histograms when you drag to snap two windows on
// opposite sides.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       BothSnappedOverviewExitAnimationHistogramTest) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> left_window(CreateAppWindow(bounds));
  std::unique_ptr<aura::Window> right_window(CreateAppWindow(bounds));
  CheckOverviewEnterExitHistogram("Init", {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0});

  ToggleOverview();
  WaitForOverviewEnterAnimation();
  CheckOverviewEnterExitHistogram("EnterOverview", {1, 0, 0, 0, 0},
                                  {0, 0, 0, 0, 0});

  DragWindowTo(GetOverviewItemForWindow(left_window.get()), gfx::PointF(0, 0));
  DragWindowTo(GetOverviewItemForWindow(right_window.get()),
               gfx::PointF(799, 300));
  WaitForOverviewExitAnimation();
  CheckOverviewEnterExitHistogram("SnapBothSides", {1, 0, 0, 0, 0},
                                  {1, 0, 0, 0, 1});
}

// Test that when overview and splitview are both active, only resize that
// happens on eligible window components will change snapped window bounds and
// overview bounds at the same time.
TEST_F(SplitViewOverviewSessionInClamshellTest, ResizeWindowTest) {
  UpdateDisplay("600x400");
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds));
  std::unique_ptr<aura::Window> window2(
      CreateWindowWithHitTestComponent(HTLEFT, bounds));
  std::unique_ptr<aura::Window> window3(
      CreateWindowWithHitTestComponent(HTTOP, bounds));
  std::unique_ptr<aura::Window> window4(
      CreateWindowWithHitTestComponent(HTBOTTOM, bounds));

  ToggleOverview();
  gfx::Rect overview_full_bounds = GetGridBounds();
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_NE(GetGridBounds(), overview_full_bounds);
  EXPECT_EQ(GetGridBounds(), GetExpectedOverviewBounds());
  gfx::Rect overview_snapped_bounds = GetGridBounds();

  // Resize that happens on the right edge of the left snapped window will
  // resize the window and overview at the same time.
  ui::test::EventGenerator generator1(Shell::GetPrimaryRootWindow(),
                                      window1.get());
  generator1.PressLeftButton();
  CheckWindowResizingPerformanceHistograms("BeforeResizingLeftSnappedWindow1",
                                           0, 0, 0, 0);
  const int drag_x(50);
  generator1.MoveMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("WhileResizingLeftSnappedWindow1", 0,
                                           0, 1, 0);
  generator1.ReleaseLeftButton();
  CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow1", 0,
                                           0, 1, 1);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_NE(GetGridBounds(), overview_full_bounds);
  EXPECT_NE(GetGridBounds(), overview_snapped_bounds);
  EXPECT_EQ(GetGridBounds(), GetExpectedOverviewBounds());
  EXPECT_TRUE(RootWindowController::ForWindow(Shell::GetPrimaryRootWindow())
                  ->split_view_overview_session());

  // Verify the overview width has decreased by the same amount the window has
  // increased.
  EXPECT_EQ(overview_snapped_bounds.width() - drag_x, GetGridBounds().width());
  const gfx::Rect work_area(
      display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
  EXPECT_EQ(work_area.width(),
            GetGridBounds().width() + window1->GetBoundsInScreen().width());

  // Resize that happens on the left edge of the left snapped window will end
  // overview. The same for the resize that happens on the top or bottom edge of
  // the left snapped window.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  DragWindowTo(overview_item2, gfx::PointF(0, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  ui::test::EventGenerator generator2(Shell::GetPrimaryRootWindow(),
                                      window2.get());
  generator2.DragMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow2", 0,
                                           0, 1, 1);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  ToggleOverview();
  auto* overview_item3 = GetOverviewItemForWindow(window3.get());
  DragWindowTo(overview_item3, gfx::PointF(0, 0));
  ui::test::EventGenerator generator3(Shell::GetPrimaryRootWindow(),
                                      window3.get());
  generator3.DragMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow3", 0,
                                           0, 1, 1);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  ToggleOverview();
  auto* overview_item4 = GetOverviewItemForWindow(window4.get());
  DragWindowTo(overview_item4, gfx::PointF(0, 0));
  ui::test::EventGenerator generator4(Shell::GetPrimaryRootWindow(),
                                      window4.get());
  generator4.DragMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow4", 0,
                                           0, 1, 1);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  // Now try snapping on the right.
  ToggleOverview();
  overview_full_bounds = GetGridBounds();
  overview_item2 = GetOverviewItemForWindow(window2.get());
  DragWindowTo(overview_item2, gfx::PointF(599, 0));
  EXPECT_NE(GetGridBounds(), overview_full_bounds);
  EXPECT_EQ(GetGridBounds(), GetExpectedOverviewBounds());
  overview_snapped_bounds = GetGridBounds();

  ui::test::EventGenerator generator5(Shell::GetPrimaryRootWindow(),
                                      window2.get());
  generator5.PressLeftButton();
  CheckWindowResizingPerformanceHistograms("BeforeResizingRightSnappedWindow2",
                                           0, 0, 1, 1);
  generator5.MoveMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("WhileResizingRightSnappedWindow2",
                                           0, 0, 2, 1);
  generator5.ReleaseLeftButton();
  CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow2",
                                           0, 0, 2, 2);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_NE(GetGridBounds(), overview_full_bounds);
  EXPECT_NE(GetGridBounds(), overview_snapped_bounds);
  EXPECT_EQ(GetGridBounds(), GetExpectedOverviewBounds());
  EXPECT_EQ(overview_snapped_bounds.width() + 50, GetGridBounds().width());
  EXPECT_EQ(work_area.width(),
            GetGridBounds().width() + window2->GetBoundsInScreen().width());

  overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(599, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  ui::test::EventGenerator generator6(Shell::GetPrimaryRootWindow(),
                                      window1.get());
  generator6.DragMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow1",
                                           0, 0, 2, 2);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  ToggleOverview();
  overview_item3 = GetOverviewItemForWindow(window3.get());
  DragWindowTo(overview_item3, gfx::PointF(599, 0));
  ui::test::EventGenerator generator7(Shell::GetPrimaryRootWindow(),
                                      window3.get());
  generator7.DragMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow3",
                                           0, 0, 2, 2);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());

  ToggleOverview();
  overview_item4 = GetOverviewItemForWindow(window4.get());
  DragWindowTo(overview_item4, gfx::PointF(599, 0));
  ui::test::EventGenerator generator8(Shell::GetPrimaryRootWindow(),
                                      window4.get());
  generator8.DragMouseBy(drag_x, 50);
  CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow4",
                                           0, 0, 2, 2);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
}

// Test closing the split view window while resizing it.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       CloseWindowWhileResizingItTest) {
  UpdateDisplay("600x400");
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> split_view_window(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds));
  std::unique_ptr<aura::Window> overview_window(CreateWindow(bounds));
  ToggleOverview();
  DragWindowTo(GetOverviewItemForWindow(split_view_window.get()),
               gfx::PointF(0.f, 0.f));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     split_view_window.get());
  generator.PressLeftButton();
  CheckWindowResizingPerformanceHistograms("AfterPressingMouseButton", 0, 0, 0,
                                           0);
  generator.MoveMouseBy(50, 50);
  CheckWindowResizingPerformanceHistograms("WhileResizing", 0, 0, 1, 0);
  split_view_window.reset();
  CheckWindowResizingPerformanceHistograms("AfterClosing", 0, 0, 1, 1);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  generator.ReleaseLeftButton();
  CheckWindowResizingPerformanceHistograms("AfterReleasingMouseButton", 0, 0, 1,
                                           1);
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
}

// Tests that there will be no crash when dragging a snapped window in overview
// toward the edge. In this case, the overview components will become too small
// to meet the minimum requirement of the fundamental UI layers such as virtual
// desk bar, shadow. See the regression behavior in http://b/324478757.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       NoCrashWhenDraggingSnappedWindowToEdge) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);

  // Create another desk to ensure the desk bar shows in overview.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());

  ToggleOverview();
  WaitForOverviewEnterAnimation();
  EXPECT_TRUE(IsInOverviewSession());
  std::unique_ptr<aura::Window> window1(
      CreateAppWindow(gfx::Rect(0, 0, 200, 100)));
  std::unique_ptr<aura::Window> window2(
      CreateAppWindow(gfx::Rect(100, 100, 200, 100)));
  const WindowSnapWMEvent event(
      WM_EVENT_SNAP_PRIMARY, chromeos::kDefaultSnapRatio,
      WindowSnapActionSource::kSnapByWindowLayoutMenu);
  auto* window1_state = WindowState::Get(window1.get());
  window1_state->OnWMEvent(&event);
  WaitForOverviewEntered();
  EXPECT_TRUE(window1_state->IsSnapped());
  EXPECT_TRUE(IsInOverviewSession());

  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(
      window1.get()->GetBoundsInScreen().right_center());
  gfx::Point drag_end_point = GetWorkAreaInScreen(window1.get()).right_center();
  drag_end_point.Offset(/*delta_x=*/-10, 0);
  event_generator->PressLeftButton();
  event_generator->MoveMouseTo(drag_end_point);
  EXPECT_TRUE(IsInOverviewSession());
  EXPECT_TRUE(WindowState::Get(window1.get())->is_dragged());

  // Verify that shadow is applied on the overview item.
  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
  const auto shadow_content_bounds =
      overview_item2->get_shadow_content_bounds_for_testing();
  EXPECT_FALSE(shadow_content_bounds.IsEmpty());
}

// Tests that when a split view window carries over to clamshell split view
// while the divider is being dragged, the window resize is properly completed.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       CarryOverToClamshellSplitViewWhileResizing) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
  WindowState* snapped_window_state = WindowState::Get(snapped_window.get());
  auto* snapped_window_state_delegate = new FakeWindowStateDelegate();
  snapped_window_state->SetDelegate(
      base::WrapUnique(snapped_window_state_delegate));

  // Enter clamshell split view and then switch to tablet mode.
  ToggleOverview();
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  EnterTabletMode();
  ASSERT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  ASSERT_EQ(snapped_window.get(), split_view_controller()->primary_window());

  // Start dragging the divider.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->set_current_screen_location(
      split_view_controller()
          ->split_view_divider()
          ->GetDividerBoundsInScreen(/*is_dragging=*/false)
          .CenterPoint());
  generator->PressTouch();
  // Drag the divider by an amount big enough to be considered
  // EventType::kGestureScrollBegin.
  generator->MoveTouchBy(7, 0);
  EXPECT_TRUE(snapped_window_state_delegate->drag_in_progress());
  EXPECT_NE(nullptr, snapped_window_state->drag_details());

  // End tablet mode.
  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
  ASSERT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  ASSERT_EQ(snapped_window.get(), split_view_controller()->primary_window());
  EXPECT_FALSE(snapped_window_state_delegate->drag_in_progress());
  EXPECT_EQ(nullptr, snapped_window_state->drag_details());
}

// Test that overview and clamshell split view end if you double click the edge
// of the split view window where it meets the overview grid.
TEST_F(SplitViewOverviewSessionInClamshellTest, HorizontalMaximizeTest) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> snapped_window(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds));
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow(bounds);
  ToggleOverview();
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  ASSERT_FALSE(split_view_controller()->IsDividerAnimating());
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  ui::test::EventGenerator(Shell::GetPrimaryRootWindow(), snapped_window.get())
      .DoubleClickLeftButton();
  ASSERT_FALSE(split_view_controller()->IsDividerAnimating());
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
}

// Test that when laptop splitview mode is active, moving the snapped window
// will end splitview and overview at the same time.
TEST_F(SplitViewOverviewSessionInClamshellTest, MoveWindowTest) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(
      CreateWindowWithHitTestComponent(HTCAPTION, bounds));
  std::unique_ptr<aura::Window> window2(
      CreateWindowWithHitTestComponent(HTCAPTION, bounds));

  ToggleOverview();
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());

  ui::test::EventGenerator generator1(Shell::GetPrimaryRootWindow(),
                                      window1.get());
  generator1.DragMouseBy(50, 50);
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
}

// Test that in clamshell splitview mode, if the snapped window is minimized,
// splitview mode and overview mode are both ended.
TEST_F(SplitViewOverviewSessionInClamshellTest, MinimizedWindowTest) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));

  ToggleOverview();
  // Drag |window1| selector item to snap to left.
  auto* overview_item1 = GetOverviewItemForWindow(window1.get());
  DragWindowTo(overview_item1, gfx::PointF(0, 0));
  EXPECT_TRUE(GetOverviewController()->InOverviewSession());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());

  // Now minimize the snapped |window1|.
  WindowState::Get(window1.get())->Minimize();
  EXPECT_FALSE(GetOverviewController()->InOverviewSession());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
}

// Test snapped window bounds with adjustment for the minimum size of a window.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       SnappedWindowBoundsWithMinimumSizeTest) {
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> window1(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds));
  const int window2_minimum_size = 350;
  std::unique_ptr<aura::Window> window2(
      CreateWindowWithMinimumSize(bounds, gfx::Size(window2_minimum_size, 0)));

  // Snap `window1` in split view, then resize it to `window1_size`, which is
  // less than `window2_minimum_size`.
  ToggleOverview();
  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
  ui::test::EventGenerator* generator = GetEventGenerator();
  ASSERT_TRUE(RootWindowController::ForWindow(window1.get())
                  ->split_view_overview_session());
  generator->MoveMouseTo(window1->GetBoundsInScreen().width(), 10);
  int window1_size = 300;
  generator->DragMouseTo(window1_size, 10);
  ASSERT_EQ(window1_size, window1->GetBoundsInScreen().width());

  // Drag `window2` to the left, on top of `window1`, to show the left highlight
  // preview.
  const gfx::Rect work_area =
      screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
          window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  const gfx::Point drag_starting_point(
      gfx::ToRoundedPoint(item2->GetTransformedBounds().CenterPoint()));
  DragItemToPoint(item2, gfx::Point(0, 0), generator,
                  /*by_touch_gestures=*/false, /*drop=*/false);

  // Test that the highlight bounds are adjusted for `window2_minimum_size`.
  gfx::Rect left_highlight_bounds(work_area.x(), work_area.y(),
                                  window2_minimum_size, work_area.height());
  left_highlight_bounds.Inset(kHighlightScreenEdgePaddingDp);
  auto* overview_grid =
      GetOverviewSession()->GetGridWithRootWindow(window1->GetRootWindow());
  EXPECT_EQ(left_highlight_bounds, overview_grid->split_view_drag_indicators()
                                       ->GetLeftHighlightViewBounds());

  // Drop the `window2` item back at its starting point.
  generator->MoveMouseTo(drag_starting_point);
  generator->ReleaseLeftButton();
  ASSERT_TRUE(RootWindowController::ForWindow(window1.get())
                  ->split_view_overview_session());

  // Now resize `window1` where `window2` can't fit in the secondary position.
  window1_size = 500;
  generator->MoveMouseTo(window1->GetBoundsInScreen().width(), 10);
  generator->DragMouseTo(window1_size, 0);
  ASSERT_TRUE(RootWindowController::ForWindow(window1.get())
                  ->split_view_overview_session());
  ASSERT_EQ(window1_size, window1->GetBoundsInScreen().width());

  // Drag `window2` to show the right highlight preview.
  DragItemToPoint(item2, work_area.top_right(), generator,
                  /*by_touch_gestures=*/false, /*drop=*/false);

  // Test that the highlight bounds are adjusted for `window2_minimum_size`.
  gfx::Rect right_highlight_bounds(work_area.right() - window2_minimum_size,
                                   work_area.y(), window2_minimum_size,
                                   work_area.height());
  right_highlight_bounds.Inset(kHighlightScreenEdgePaddingDp);
  EXPECT_EQ(right_highlight_bounds,
            overview_grid->split_view_drag_indicators()
                ->GetRightHighlightViewBoundsForTesting());
  generator->ReleaseLeftButton();
}

// Tests that on a display in portrait orientation, clamshell split view still
// uses snap positions on the left and right.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       PortraitClamshellSplitViewSnapPositionsTest) {
  UpdateDisplay("800x600/l");
  const int height = 800 - ShelfConfig::Get()->shelf_size();
  ASSERT_EQ(gfx::Rect(0, 0, 600, height),
            screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
                Shell::GetPrimaryRootWindow()));
  // Check that snapped window bounds represent snapping on the left and right.
  const gfx::Rect top_snapped_bounds(600, height / 2);
  const gfx::Rect bottom_snapped_bounds(0, height / 2, 600, height / 2);
  const gfx::Rect left_snapped_bounds(300, height);
  const gfx::Rect right_snapped_bounds(300, 0, 300, height);
  EXPECT_EQ(
      top_snapped_bounds,
      split_view_controller()->GetSnappedWindowBoundsInScreen(
          SnapPosition::kPrimary,
          /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
          /*account_for_divider_width=*/false));
  EXPECT_EQ(
      bottom_snapped_bounds,
      split_view_controller()->GetSnappedWindowBoundsInScreen(
          SnapPosition::kSecondary,
          /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
          /*account_for_divider_width=*/false));

  // Switch from clamshell mode to tablet mode and then back to clamshell mode.
  display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
      .SetFirstDisplayAsInternalDisplay();
  TabletModeControllerTestApi tablet_mode_controller_test_api;
  tablet_mode_controller_test_api.DetachAllMice();
  EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
  tablet_mode_controller_test_api.OpenLidToAngle(315.0f);
  EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
  tablet_mode_controller_test_api.OpenLidToAngle(90.0f);
  EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
  // Check the snapped window bounds again. They should be the same as before.
  EXPECT_EQ(
      top_snapped_bounds,
      split_view_controller()->GetSnappedWindowBoundsInScreen(
          SnapPosition::kPrimary,
          /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
          /*account_for_divider_width=*/false));
  EXPECT_EQ(
      bottom_snapped_bounds,
      split_view_controller()->GetSnappedWindowBoundsInScreen(
          SnapPosition::kSecondary,
          /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
          /*account_for_divider_width=*/false));
}

// Tests that the ratio between the divider position and the work area width is
// the same before and after changing the display orientation in clamshell mode.
TEST_F(SplitViewOverviewSessionInClamshellTest, DisplayOrientationChangeTest) {
  UpdateDisplay("600x400");
  const gfx::Rect bounds(400, 400);
  std::unique_ptr<aura::Window> split_view_window(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds));
  std::unique_ptr<aura::Window> overview_window(CreateWindow(bounds));
  ToggleOverview();
  split_view_controller()->SnapWindow(split_view_window.get(),
                                      SnapPosition::kPrimary);
  const auto test_many_orientation_changes =
      [this](const std::string& description) {
        SCOPED_TRACE(description);
        for (display::Display::Rotation rotation :
             {display::Display::ROTATE_270, display::Display::ROTATE_180,
              display::Display::ROTATE_90, display::Display::ROTATE_0,
              display::Display::ROTATE_180, display::Display::ROTATE_0}) {
          const auto compute_window_snap_ratio = [this]() {
            const display::Display& display =
                display::Screen::GetScreen()->GetPrimaryDisplay();
            const bool is_horizontal = IsLayoutHorizontal(display);
            const gfx::Rect work_area = display.work_area();
            const int size =
                is_horizontal ? work_area.width() : work_area.height();
            const gfx::Rect window_bounds(split_view_controller()
                                              ->GetDefaultSnappedWindow()
                                              ->GetBoundsInScreen());
            const int window_size =
                is_horizontal ? window_bounds.width() : window_bounds.height();
            return static_cast<float>(window_size) / static_cast<float>(size);
          };
          const float before = compute_window_snap_ratio();
          Shell::Get()->display_manager()->SetDisplayRotation(
              display::Screen::GetScreen()->GetPrimaryDisplay().id(), rotation,
              display::Display::RotationSource::ACTIVE);
          const float after = compute_window_snap_ratio();
          EXPECT_NEAR(before, after, 0.001f);
        }
      };

  const gfx::Rect work_area =
      display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
  test_many_orientation_changes("centered divider");
  EXPECT_EQ(split_view_window->GetBoundsInScreen().width() * 2,
            work_area.width());
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(
      split_view_window->GetBoundsInScreen().right_center());
  event_generator->DragMouseBy(50, 50);
  EXPECT_NE(split_view_window->GetBoundsInScreen().width() * 2,
            work_area.width());
  test_many_orientation_changes("off-center divider");
}

// Verify that an item's unsnappable indicator is updated for display rotation.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       OverviewUnsnappableIndicatorVisibilityAfterDisplayRotation) {
  UpdateDisplay("900x600");
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  // Because of its minimum size, |overview_window| is snappable in clamshell
  // split view with landscape display orientation but not with portrait display
  // orientation.
  std::unique_ptr<aura::Window> overview_window(
      CreateWindowWithMinimumSize(gfx::Rect(400, 400), gfx::Size(400, 500)));
  ToggleOverview();
  ASSERT_TRUE(GetOverviewController()->InOverviewSession());
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  ASSERT_TRUE(split_view_controller()->InSplitViewMode());
  auto* overview_item = GetOverviewItemForWindow(overview_window.get());
  // Note: |cannot_snap_label_view_| and its parent will be created on demand.
  EXPECT_FALSE(GetCannotSnapWidget(overview_item));

  // Rotate to primary portrait orientation. The unsnappable indicator appears.
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  const int64_t display_id =
      display::Screen::GetScreen()->GetPrimaryDisplay().id();
  display_manager->SetDisplayRotation(display_id, display::Display::ROTATE_270,
                                      display::Display::RotationSource::ACTIVE);
  views::Widget* cannot_snap_widget = GetCannotSnapWidget(overview_item);
  ASSERT_TRUE(cannot_snap_widget);
  ui::Layer* unsnappable_layer = cannot_snap_widget->GetLayer();
  EXPECT_EQ(1.f, unsnappable_layer->opacity());

  // Rotate to primary landscape orientation. The unsnappable indicator hides.
  display_manager->SetDisplayRotation(display_id, display::Display::ROTATE_0,
                                      display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(0.f, unsnappable_layer->opacity());
}

// Tests that dragging a window from overview creates a drop target on the same
// display, even if the window bounds are mostly on another display.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       DragFromOverviewWithBoundsMostlyOnAnotherDisplay) {
  UpdateDisplay("700x600,700x600");
  const aura::Window::Windows root_windows = Shell::Get()->GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const display::DisplayIdList display_ids =
      display_manager()->GetConnectedDisplayIdList();
  ASSERT_EQ(2u, display_ids.size());
  ASSERT_EQ(root_windows[0], Shell::GetRootWindowForDisplayId(display_ids[0]));
  ASSERT_EQ(root_windows[1], Shell::GetRootWindowForDisplayId(display_ids[1]));

  display::Screen* screen = display::Screen::GetScreen();
  const gfx::Rect creation_bounds(0, 0, 600, 600);
  ASSERT_EQ(display_ids[0], screen->GetDisplayMatching(creation_bounds).id());
  const gfx::Rect bounds(550, 0, 600, 600);
  ASSERT_EQ(display_ids[1], screen->GetDisplayMatching(bounds).id());
  std::unique_ptr<aura::Window> window = CreateTestWindow(creation_bounds);
  window->SetBoundsInScreen(bounds,
                            display_manager()->GetDisplayForId(display_ids[0]));

  ToggleOverview();
  auto* overview_item = GetOverviewItemForWindow(window.get());
  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
  gfx::PointF drag_point = overview_item->target_bounds().CenterPoint();
  GetOverviewSession()->InitiateDrag(overview_item, drag_point,
                                     /*is_touch_dragging=*/false,
                                     /*event_source_item=*/overview_item);
  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
  drag_point.Offset(5.f, 0.f);
  GetOverviewSession()->Drag(overview_item, drag_point);
  EXPECT_FALSE(GetDropTarget(1));
  ASSERT_TRUE(GetDropTarget(0));
  EXPECT_EQ(root_windows[0], GetDropTarget(0)->root_window());
  GetOverviewSession()->CompleteDrag(overview_item, drag_point);
  EXPECT_FALSE(GetDropTarget(0));
  EXPECT_FALSE(GetDropTarget(1));
}

// Tests that cycle snap do not start overview.
TEST_F(SplitViewOverviewSessionInClamshellTest, CycleSnapNotStartOverview) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  wm::ActivateWindow(window1.get());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(InOverviewSession());

  const WindowSnapWMEvent cycle_snap_primary(WM_EVENT_CYCLE_SNAP_PRIMARY);
  WindowState* window1_state = WindowState::Get(window1.get());
  window1_state->OnWMEvent(&cycle_snap_primary);
  EXPECT_EQ(WindowStateType::kPrimarySnapped, window1_state->GetStateType());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(InOverviewSession());

  const WindowSnapWMEvent cycle_snap_secondary(WM_EVENT_CYCLE_SNAP_SECONDARY);
  window1_state->OnWMEvent(&cycle_snap_secondary);
  EXPECT_EQ(WindowStateType::kSecondarySnapped, window1_state->GetStateType());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(InOverviewSession());
}

// Tests using Alt+[ on a left split view window.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       AltLeftSquareBracketOnLeftSplitViewWindow) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
  ToggleOverview();
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kPrimary);
  WindowState* snapped_window_state = WindowState::Get(snapped_window.get());
  EXPECT_EQ(WindowStateType::kPrimarySnapped,
            snapped_window_state->GetStateType());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(InOverviewSession());
  const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY);
  snapped_window_state->OnWMEvent(&alt_left_square_bracket);
  EXPECT_EQ(WindowStateType::kNormal, snapped_window_state->GetStateType());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(InOverviewSession());
}

// Tests using Alt+] on a right split view window.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       AltRightSquareBracketOnRightSplitViewWindow) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
  ToggleOverview();
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kSecondary);
  WindowState* snapped_window_state = WindowState::Get(snapped_window.get());
  EXPECT_EQ(WindowStateType::kSecondarySnapped,
            snapped_window_state->GetStateType());
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(InOverviewSession());
  const WindowSnapWMEvent alt_right_square_bracket(
      WM_EVENT_CYCLE_SNAP_SECONDARY);
  snapped_window_state->OnWMEvent(&alt_right_square_bracket);
  EXPECT_EQ(WindowStateType::kNormal, snapped_window_state->GetStateType());
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(InOverviewSession());
}

// Tests using Alt+[ on a right split view window, and Alt+] on a left split
// view window.
TEST_F(SplitViewOverviewSessionInClamshellTest,
       AltSquareBracketOnSplitViewWindow) {
  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
  // Enter clamshell split view with |snapped_window| on the right.
  ToggleOverview();
  split_view_controller()->SnapWindow(snapped_window.get(),
                                      SnapPosition::kSecondary);
  wm::ActivateWindow(snapped_window.get());
  WindowState* snapped_window_state = WindowState::Get(snapped_window.get());
  EXPECT_EQ(WindowStateType::kSecondarySnapped,
            snapped_window_state->GetStateType());
  EXPECT_EQ(SplitViewController::State::kSecondarySnapped,
            split_view_controller()->state());
  EXPECT_EQ(snapped_window.get(), split_view_controller()->secondary_window());
  EXPECT_TRUE(InOverviewSession());
  // Test using Alt+[ to put |snapped_window| on the left.
  const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY);
  snapped_window_state->OnWMEvent(&alt_left_square_bracket);
  EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get()));
  EXPECT_EQ(WindowStateType::kPrimarySnapped,
            snapped_window_state->GetStateType());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  EXPECT_EQ(snapped_window.get(), split_view_controller()->primary_window());
  EXPECT_TRUE(InOverviewSession());
  // Test using Alt+] to put |snapped_window| on the right.
  const WindowSnapWMEvent alt_right_square_bracket(
      WM_EVENT_CYCLE_SNAP_SECONDARY);
  snapped_window_state->OnWMEvent(&alt_right_square_bracket);
  EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get()));
  EXPECT_EQ(WindowStateType::kSecondarySnapped,
            snapped_window_state->GetStateType());
  EXPECT_EQ(SplitViewController::State::kSecondarySnapped,
            split_view_controller()->state());
  EXPECT_EQ(snapped_window.get(), split_view_controller()->secondary_window());
  EXPECT_TRUE(InOverviewSession());
}

using SplitViewOverviewSessionInClamshellTestMultiDisplayOnly =
    SplitViewOverviewSessionInClamshellTest;

// Test |SplitViewController::Get|.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       GetSplitViewController) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2);
  EXPECT_EQ(root_windows[0],
            SplitViewController::Get(window1.get())->root_window());
  EXPECT_EQ(root_windows[1],
            SplitViewController::Get(window2.get())->root_window());
}

// Test |SplitViewController::GetSnappedWindowBoundsInScreen|.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       GetSnappedBounds) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const int height = 600 - ShelfConfig::Get()->shelf_size();
  ASSERT_EQ(gfx::Rect(0, 0, 800, height),
            screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
                root_windows[0]));
  ASSERT_EQ(gfx::Rect(800, 0, 800, height),
            screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
                root_windows[1]));

  EXPECT_EQ(
      gfx::Rect(0, 0, 400, height),
      SplitViewController::Get(root_windows[0])
          ->GetSnappedWindowBoundsInScreen(
              SnapPosition::kPrimary,
              /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
              /*account_for_divider_width=*/false));
  EXPECT_EQ(
      gfx::Rect(400, 0, 400, height),
      SplitViewController::Get(root_windows[0])
          ->GetSnappedWindowBoundsInScreen(
              SnapPosition::kSecondary,
              /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
              /*account_for_divider_width=*/false));
  EXPECT_EQ(
      gfx::Rect(800, 0, 400, height),
      SplitViewController::Get(root_windows[1])
          ->GetSnappedWindowBoundsInScreen(
              SnapPosition::kPrimary,
              /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
              /*account_for_divider_width=*/false));
  EXPECT_EQ(
      gfx::Rect(1200, 0, 400, height),
      SplitViewController::Get(root_windows[1])
          ->GetSnappedWindowBoundsInScreen(
              SnapPosition::kSecondary,
              /*window_for_minimum_size=*/nullptr, chromeos::kDefaultSnapRatio,
              /*account_for_divider_width=*/false));
}

// Test that if clamshell split view is started by snapping a window that is the
// only overview window, then split view ends as soon as it starts, and overview
// ends along with it.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       SplitViewEndsImmediatelyIfOverviewIsEmpty) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  std::unique_ptr<aura::Window> window = CreateTestWindow(bounds_within_root1);
  ToggleOverview();
  SplitViewController::Get(root_windows[0])
      ->SnapWindow(window.get(), SnapPosition::kPrimary);
  EXPECT_FALSE(InOverviewSession());
  EXPECT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
}

// Test that if clamshell split view is started by snapping a window on one
// display while there is an overview window on another display, then split view
// stays active (instead of ending as soon as it starts), and overview also
// stays active. Then close the overview window and verify that split view and
// overview are ended.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       SplitViewViableWithOverviewWindowOnOtherDisplay) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2);
  ToggleOverview();
  SplitViewController::Get(root_windows[0])
      ->SnapWindow(window1.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  EXPECT_TRUE(InOverviewSession());
  window2.reset();
  EXPECT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  EXPECT_FALSE(InOverviewSession());
}

// Test dragging to snap an overview item on an external display.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       DraggingOnExternalDisplay) {
  UpdateDisplay("800x600,800+0-800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());

  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2);

  ToggleOverview();

  OverviewGrid* grid_on_root2 =
      GetOverviewSession()->GetGridWithRootWindow(root_windows[1]);
  auto* item1 = grid_on_root2->GetOverviewItemContaining(window1.get());
  auto* item2 = grid_on_root2->GetOverviewItemContaining(window2.get());
  SplitViewController* split_view_controller =
      SplitViewController::Get(root_windows[1]);
  const SplitViewDragIndicators* indicators =
      grid_on_root2->split_view_drag_indicators();

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      gfx::ToRoundedPoint(item1->target_bounds().CenterPoint()));
  event_generator->PressLeftButton();

  // TODO(http://b/300700394): Avoid hardcoding these numbers.
  const gfx::Point right_snap_point(1599, 300);
  event_generator->MoveMouseTo(right_snap_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapSecondary,
            indicators->current_window_dragging_state());
  EXPECT_EQ(
      SplitViewController::Get(root_windows[1])
          ->GetSnappedWindowBoundsInScreen(SnapPosition::kPrimary,
                                           /*window_for_minimum_size=*/nullptr,
                                           chromeos::kDefaultSnapRatio,
                                           /*account_for_divider_width=*/false),
      OverviewGridTestApi(grid_on_root2).bounds());
  event_generator->ReleaseLeftButton();
  EXPECT_EQ(SplitViewController::State::kSecondarySnapped,
            split_view_controller->state());
  EXPECT_EQ(window1.get(), split_view_controller->secondary_window());

  event_generator->MoveMouseTo(
      gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
  GetEventGenerator()->PressLeftButton();
  const gfx::Point left_of_middle(1150, 300);
  event_generator->MoveMouseTo(left_of_middle);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
            indicators->current_window_dragging_state());
  event_generator->ReleaseLeftButton();
  EXPECT_EQ(SplitViewController::State::kSecondarySnapped,
            split_view_controller->state());
  EXPECT_EQ(window1.get(), split_view_controller->secondary_window());

  event_generator->MoveMouseTo(
      gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
  event_generator->PressLeftButton();
  const gfx::Point left_snap_point(810, 300);
  event_generator->MoveMouseTo(left_snap_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
            indicators->current_window_dragging_state());
  event_generator->ReleaseLeftButton();
  EXPECT_EQ(SplitViewController::State::kNoSnap,
            split_view_controller->state());
}

// Test dragging from one display to another.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       MultiDisplayDragging) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const display::Display display_with_root1 =
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[0]);
  const display::Display display_with_root2 =
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]);
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2);
  ToggleOverview();
  OverviewGrid* grid_on_root1 =
      GetOverviewSession()->GetGridWithRootWindow(root_windows[0]);
  OverviewGrid* grid_on_root2 =
      GetOverviewSession()->GetGridWithRootWindow(root_windows[1]);
  auto* item1 = grid_on_root1->GetOverviewItemContaining(window1.get());
  const SplitViewDragIndicators* indicators_on_root1 =
      grid_on_root1->split_view_drag_indicators();
  const SplitViewDragIndicators* indicators_on_root2 =
      grid_on_root2->split_view_drag_indicators();

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      gfx::ToRoundedPoint(item1->target_bounds().CenterPoint()));
  event_generator->PressLeftButton();
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(display_with_root1.work_area(),
            OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(display_with_root2.work_area(),
            OverviewGridTestApi(grid_on_root2).bounds());

  const gfx::Point root1_left_snap_point(0, 300);
  event_generator->MoveMouseTo(root1_left_snap_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(
      SplitViewController::Get(root_windows[0])
          ->GetSnappedWindowBoundsInScreen(SnapPosition::kSecondary,
                                           /*window_for_minimum_size=*/nullptr,
                                           chromeos::kDefaultSnapRatio,
                                           /*account_for_divider_width=*/false),
      OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(display_with_root2.work_area(),
            OverviewGridTestApi(grid_on_root2).bounds());

  const gfx::Point root1_middle_point(400, 300);
  event_generator->MoveMouseTo(root1_middle_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(display_with_root1.work_area(),
            OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(display_with_root2.work_area(),
            OverviewGridTestApi(grid_on_root2).bounds());

  const gfx::Point root1_right_snap_point(799, 300);
  event_generator->MoveMouseTo(root1_right_snap_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapSecondary,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(
      SplitViewController::Get(root_windows[0])
          ->GetSnappedWindowBoundsInScreen(SnapPosition::kPrimary,
                                           /*window_for_minimum_size=*/nullptr,
                                           chromeos::kDefaultSnapRatio,
                                           /*account_for_divider_width=*/false),
      OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(display_with_root2.work_area(),
            OverviewGridTestApi(grid_on_root2).bounds());

  const gfx::Point root2_left_snap_point(800, 300);
  event_generator->MoveMouseTo(root2_left_snap_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(display_with_root1.work_area(),
            OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(
      SplitViewController::Get(root_windows[1])
          ->GetSnappedWindowBoundsInScreen(SnapPosition::kSecondary,
                                           /*window_for_minimum_size=*/nullptr,
                                           chromeos::kDefaultSnapRatio,
                                           /*account_for_divider_width=*/false),
      OverviewGridTestApi(grid_on_root2).bounds());

  const gfx::Point root2_left_snap_point_away_from_edge(816, 300);
  event_generator->MoveMouseTo(root2_left_snap_point_away_from_edge);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(display_with_root1.work_area(),
            OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(
      SplitViewController::Get(root_windows[1])
          ->GetSnappedWindowBoundsInScreen(SnapPosition::kSecondary,
                                           /*window_for_minimum_size=*/nullptr,
                                           chromeos::kDefaultSnapRatio,
                                           /*account_for_divider_width=*/false),
      OverviewGridTestApi(grid_on_root2).bounds());

  const gfx::Point root2_right_snap_point(1599, 300);
  event_generator->MoveMouseTo(root2_right_snap_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(display_with_root1.work_area(),
            OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapSecondary,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(
      SplitViewController::Get(root_windows[1])
          ->GetSnappedWindowBoundsInScreen(SnapPosition::kPrimary,
                                           /*window_for_minimum_size=*/nullptr,
                                           chromeos::kDefaultSnapRatio,
                                           /*account_for_divider_width=*/false),
      OverviewGridTestApi(grid_on_root2).bounds());

  const gfx::Point root2_middle_point(1200, 300);
  event_generator->MoveMouseTo(root2_middle_point);
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(display_with_root1.work_area(),
            OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(display_with_root2.work_area(),
            OverviewGridTestApi(grid_on_root2).bounds());

  event_generator->ReleaseLeftButton();
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag,
            indicators_on_root1->current_window_dragging_state());
  EXPECT_EQ(display_with_root1.work_area(),
            OverviewGridTestApi(grid_on_root1).bounds());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag,
            indicators_on_root2->current_window_dragging_state());
  EXPECT_EQ(display_with_root2.work_area(),
            OverviewGridTestApi(grid_on_root2).bounds());
}

// Verify the drop target positions for multi-display dragging.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       DropTargetPositionTest) {
  wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager();
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const display::Display display_with_root1 =
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[0]);
  const display::Display display_with_root2 =
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]);
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  // Named for MRU order, which is in reverse of creation order.
  std::unique_ptr<aura::Window> window6 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  ToggleOverview();
  OverviewGrid* grid1 =
      GetOverviewSession()->GetGridWithRootWindow(root_windows[0]);
  OverviewGrid* grid2 =
      GetOverviewSession()->GetGridWithRootWindow(root_windows[1]);
  auto* item4 = grid2->GetOverviewItemContaining(window4.get());
  // Start dragging |item4| from |grid2|.
  cursor_manager->SetDisplay(display_with_root2);
  GetOverviewSession()->InitiateDrag(
      item4, item4->target_bounds().CenterPoint(),
      /*is_touch_dragging=*/false, /*event_source_item=*/item4);
  GetOverviewSession()->Drag(item4, gfx::PointF(1200.f, 0.f));
  // On the grid where the drag starts (|grid2|), the drop target is inserted at
  // the index immediately following the dragged item (|item4|).
  ASSERT_EQ(4u, grid2->item_list().size());
  EXPECT_EQ(GetDropTarget(1), grid2->item_list()[2].get());
  // Drag over |grid1|.
  cursor_manager->SetDisplay(display_with_root1);
  GetOverviewSession()->Drag(item4, gfx::PointF(400.f, 0.f));
  // On other grids (such as |grid1|), the drop target is inserted at the
  // correct position according to MRU order (between the overview items for
  // |window3| and |window5|).
  ASSERT_EQ(4u, grid1->item_list().size());
  EXPECT_EQ(GetDropTarget(0), grid1->item_list()[2].get());
}

// Verify that the drop target in each overview grid has the correct bounds when
// a maximized window is being dragged.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       DropTargetBoundsForMaximizedWindowDraggedToOtherDisplay) {
  UpdateDisplay("1200x400,1200x400/l");
  std::unique_ptr<aura::Window> window = CreateTestWindow();
  WindowState::Get(window.get())->Maximize();
  ToggleOverview();
  auto* item = GetOverviewItemForWindow(window.get());
  // Verify that |item| is letter boxed. The bounds of |item|, minus the margin
  // should have an aspect ratio of 2 : 1.
  gfx::RectF item_bounds = item->target_bounds();
  EXPECT_EQ(OverviewItemFillMode::kLetterBoxed,
            item->GetOverviewItemFillMode());
  EXPECT_EQ(2.f, item_bounds.width() / item_bounds.height());
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::ToRoundedPoint(item_bounds.CenterPoint()));
  event_generator->PressLeftButton();

  // Drag to the middle of the secondary display to avoid triggering the drag
  // snap indicator animation.
  event_generator->MoveMouseTo(gfx::Point(1400, 500));

  auto* drop_target = GetDropTarget(1);
  ASSERT_TRUE(drop_target);

  // Verify that `drop_target` is effectively pillar boxed. Avoid calling
  // `OverviewItem::GetOverviewItemFillMode()`, because it does not work for
  // drop targets (and that is okay). The bounds of `drop_target`, minus the
  // margin should have an aspect ratio of 1 : 2.
  const gfx::Size drop_target_size =
      drop_target->item_widget()->GetWindowBoundsInScreen().size();
  EXPECT_EQ(0.5f, static_cast<float>(drop_target_size.width()) /
                      drop_target_size.height());
}

// Verify that the drop target in each overview grid has bounds representing
// anticipation that if the dragged window is dropped into that grid, it will
// shrink to fit into the corresponding work area.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       DropTargetBoundsOnDisplayWhereDraggedWindowDoesNotFitIntoWorkArea) {
  UpdateDisplay("600x500,600+0-1200x1000");
  // Drags `item` from the right display to the left display and back, and
  // returns the bounds of the drop target that appears on the left display.
  const auto root1_drop_target_bounds = [this](OverviewItemBase* item) {
    const gfx::Point drag_starting_point =
        gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
    auto* event_generator = GetEventGenerator();
    event_generator->MoveMouseTo(drag_starting_point);
    event_generator->PressLeftButton();
    event_generator->MoveMouseTo(gfx::Point(300, 0));
    event_generator->MoveMouseTo(drag_starting_point);

    CHECK(GetDropTarget(0));
    const gfx::RectF result = GetDropTarget(0)->target_bounds();
    event_generator->ReleaseLeftButton();
    return result;
  };

  // |window1| has the size that |window2| would become if moved to the left
  // display.
  std::unique_ptr<aura::Window> window1 =
      CreateTestWindow(gfx::Rect(600, 0, 600, 400));
  std::unique_ptr<aura::Window> window2 =
      CreateTestWindow(gfx::Rect(600, 0, 1000, 400));
  // |window3| has the size that |window4| would become if moved to the left
  // display.
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(
      gfx::Rect(600, 0, 400, 600 - ShelfConfig::Get()->shelf_size()));
  std::unique_ptr<aura::Window> window4 =
      CreateTestWindow(gfx::Rect(600, 0, 400, 1000));

  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());
  auto* item4 = GetOverviewItemForWindow(window4.get());

  // For good test coverage in each case, the dragged window and the drop target
  // have different `OverviewItemFillMode` values.
  EXPECT_EQ(OverviewItemFillMode::kNormal, item1->GetOverviewItemFillMode());
  EXPECT_EQ(OverviewItemFillMode::kLetterBoxed,
            item2->GetOverviewItemFillMode());
  EXPECT_EQ(OverviewItemFillMode::kNormal, item3->GetOverviewItemFillMode());
  EXPECT_EQ(OverviewItemFillMode::kPillarBoxed,
            item4->GetOverviewItemFillMode());

  EXPECT_EQ(root1_drop_target_bounds(item1), root1_drop_target_bounds(item2));
  EXPECT_EQ(root1_drop_target_bounds(item3), root1_drop_target_bounds(item4));
}

// Test dragging from one overview grid and dropping into another overview grid.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       DragAndDropIntoAnotherOverviewGrid) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  std::unique_ptr<aura::Window> window = CreateTestWindow();
  ASSERT_EQ(root_windows[0], window->GetRootWindow());
  ToggleOverview();
  OverviewGrid* grid1 =
      GetOverviewSession()->GetGridWithRootWindow(root_windows[0]);
  OverviewGrid* grid2 =
      GetOverviewSession()->GetGridWithRootWindow(root_windows[1]);

  // Drag |window| from |grid1| and drop into |grid2|.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(
      gfx::ToRoundedPoint(grid1->GetOverviewItemContaining(window.get())
                              ->target_bounds()
                              .CenterPoint()));
  generator->PressLeftButton();
  Shell::Get()->cursor_manager()->SetDisplay(
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]));
  generator->MoveMouseTo(1200, 300);
  generator->ReleaseLeftButton();

  EXPECT_EQ(root_windows[1], window->GetRootWindow());
  EXPECT_TRUE(grid1->empty());
  auto* item = grid2->GetOverviewItemContaining(window.get());
  ASSERT_TRUE(item);
  EXPECT_EQ(root_windows[1], item->root_window());
}

// Test that overview widgets are stacked in the correct order after an overview
// window is dragged from one overview grid and dropped into another. Also test
// that the destination overview grid is arranged in the correct order.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       OverviewWidgetStackingOrderAndGridOrderWithMultiDisplayDragging) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root2);
  aura::Window* parent_on_root1 = window2->parent();
  aura::Window* parent_on_root2 = window1->parent();
  ASSERT_NE(parent_on_root1, parent_on_root2);
  ASSERT_EQ(window3->parent(), parent_on_root2);
  ToggleOverview();
  auto* item1 = GetOverviewItemForWindow(window1.get());
  auto* item2 = GetOverviewItemForWindow(window2.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());

  ASSERT_EQ(root_windows[0], item2->root_window());
  // Verify that |item1| is stacked above |item3| (because we created |window1|
  // after |window3|).
  EXPECT_TRUE(
      window_util::IsStackedBelow(item3->item_widget()->GetNativeWindow(),
                                  item1->item_widget()->GetNativeWindow()));

  // Verify that the item widget for each window is stacked below that window.
  EXPECT_TRUE(window_util::IsStackedBelow(
      item1->item_widget()->GetNativeWindow(), window1.get()));
  EXPECT_TRUE(window_util::IsStackedBelow(
      item2->item_widget()->GetNativeWindow(), window2.get()));
  EXPECT_TRUE(window_util::IsStackedBelow(
      item3->item_widget()->GetNativeWindow(), window3.get()));

  // Drag |item2| from the left display and drop into the right display.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(
      gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  Shell::Get()->cursor_manager()->SetDisplay(
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]));
  generator->MoveMouseTo(1200, 300);
  generator->ReleaseLeftButton();
  // |item2| is now a dangling pointer and we have to refresh it, because when
  // an overview window is dragged from one grid and dropped into another, the
  // original item is destroyed and a new one is created.
  item2 = GetOverviewItemForWindow(window2.get());

  ASSERT_EQ(window2->parent(), parent_on_root2);
  ASSERT_EQ(root_windows[1], item2->root_window());
  // With all three items on one grid, verify that their stacking order
  // corresponds to the MRU order of the windows. The new |item2| is sandwiched
  // between |item1| and |item3|.
  EXPECT_TRUE(
      window_util::IsStackedBelow(item2->item_widget()->GetNativeWindow(),
                                  item1->item_widget()->GetNativeWindow()));
  EXPECT_TRUE(
      window_util::IsStackedBelow(item3->item_widget()->GetNativeWindow(),
                                  item2->item_widget()->GetNativeWindow()));
  // Verify that the item widget for the new |item2| is stacked below |window2|.
  EXPECT_TRUE(window_util::IsStackedBelow(
      item2->item_widget()->GetNativeWindow(), window2.get()));

  // Verify that the right grid is in MRU order.
  const std::vector<aura::Window*> expected_order = {
      window1.get(), window2.get(), window3.get()};
  EXPECT_EQ(expected_order, GetWindowsListInOverviewGrids());
}

// Test dragging from one display to another and then snapping.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       DragFromOneDisplayToAnotherAndSnap) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  SplitViewController* split_view_controller1 =
      SplitViewController::Get(root_windows[0]);
  SplitViewController* split_view_controller2 =
      SplitViewController::Get(root_windows[1]);
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1);
  ToggleOverview();
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(gfx::ToRoundedPoint(
      GetOverviewItemForWindow(window2.get())->target_bounds().CenterPoint()));
  generator->PressLeftButton();
  Shell::Get()->cursor_manager()->SetDisplay(
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]));
  generator->MoveMouseTo(800, 300);
  generator->ReleaseLeftButton();
  EXPECT_EQ(SplitViewController::State::kNoSnap,
            split_view_controller1->state());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller2->state());
  EXPECT_EQ(window2.get(), split_view_controller2->primary_window());
  EXPECT_EQ(root_windows[1], window2->GetRootWindow());
  EXPECT_TRUE(InOverviewSession());
}

// Verify that window resizing performance is recorded to the correct histogram
// depending on whether the overview grid is empty.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       WindowResizingPerformanceHistogramsTest) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window1(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds_within_root1));
  std::unique_ptr<aura::Window> window2(
      CreateWindowWithHitTestComponent(HTRIGHT, bounds_within_root2));
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2);
  ToggleOverview();
  SplitViewController::Get(root_windows[0])
      ->SnapWindow(window1.get(), SnapPosition::kPrimary);
  SplitViewController::Get(root_windows[1])
      ->SnapWindow(window2.get(), SnapPosition::kPrimary);
  // Resize |window1|, which is in split view with an empty overview grid.
  ui::test::EventGenerator generator1(root_windows[0], window1.get());
  generator1.PressLeftButton();
  CheckWindowResizingPerformanceHistograms("BeforeResizingWindow1", 0, 0, 0, 0);
  generator1.MoveMouseBy(50, 50);
  CheckWindowResizingPerformanceHistograms("WhileResizingWindow1", 1, 0, 0, 0);
  generator1.ReleaseLeftButton();
  CheckWindowResizingPerformanceHistograms("AfterResizingWindow1", 1, 1, 0, 0);
  // Resize |window2|, which is in split view with a nonempty overview grid.
  Shell::Get()->cursor_manager()->SetDisplay(
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]));
  ui::test::EventGenerator generator2(root_windows[1], window2.get());
  generator2.PressLeftButton();
  CheckWindowResizingPerformanceHistograms("BeforeResizingWindow2", 1, 1, 0, 0);
  generator2.MoveMouseBy(50, 50);
  CheckWindowResizingPerformanceHistograms("WhileResizingWindow2", 1, 1, 1, 0);
  generator2.ReleaseLeftButton();
  CheckWindowResizingPerformanceHistograms("AfterResizingWindow2", 1, 1, 1, 1);
}

// Verify that the user action "SplitView_MultiDisplaySplitView" is recorded
// when multi-display split view starts, and that a value is recorded to the
// histogram "Ash.SplitView.TimeInMultiDisplaySplitView" when multi-display
// split view ends. This test does not actually examine the timing values
// recorded to the histogram, but this test does provide evidence of timing
// accuracy as the time in multi-display split view is measured from the time
// when the user action "SplitView_MultiDisplaySplitView" is recorded.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       MultiDisplaySplitViewMetrics) {
  base::UserActionTester user_action_tester;
  base::HistogramTester histogram_tester;
  // Verifies that multi-display split view has started exactly |start_count|
  // times and ended exactly |end_count| times. If not, then the output will
  // include |description| to indicate where the test failed.
  const auto verify = [&user_action_tester, &histogram_tester](
                          const char* description, int start_count,
                          int end_count) {
    SCOPED_TRACE(description);
    EXPECT_EQ(start_count, user_action_tester.GetActionCount(
                               "SplitView_MultiDisplaySplitView"));
    histogram_tester.ExpectTotalCount(
        "Ash.SplitView.TimeInMultiDisplaySplitView", end_count);
  };

  UpdateDisplay("800x600,800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(3u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  const gfx::Rect bounds_within_root3(1600, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds_within_root3);
  SplitViewController* split_view_controller1 =
      SplitViewController::Get(root_windows[0]);
  SplitViewController* split_view_controller2 =
      SplitViewController::Get(root_windows[1]);
  SplitViewController* split_view_controller3 =
      SplitViewController::Get(root_windows[2]);
  verify("1. Unit test set up", 0, 0);
  ToggleOverview();
  split_view_controller1->SnapWindow(window1.get(), SnapPosition::kPrimary);
  verify("2. Number of displays in split view changed from 0 to 1", 0, 0);
  split_view_controller2->SnapWindow(window3.get(), SnapPosition::kPrimary);
  verify("3. Number of displays in split view changed from 1 to 2", 1, 0);
  ToggleOverview();
  verify("4. Number of displays in split view changed from 2 to 0", 1, 1);
  ToggleOverview();
  split_view_controller1->SnapWindow(window1.get(), SnapPosition::kPrimary);
  verify("5. Number of displays in split view changed from 0 to 1", 1, 1);
  split_view_controller2->SnapWindow(window3.get(), SnapPosition::kPrimary);
  verify("6. Number of displays in split view changed from 1 to 2", 2, 1);
  split_view_controller3->SnapWindow(window5.get(), SnapPosition::kPrimary);
  verify("7. Number of displays in split view changed from 2 to 3", 2, 1);
  ToggleOverview();
  verify("8. Number of displays in split view changed from 3 to 0", 2, 2);
  ToggleOverview();
  split_view_controller1->SnapWindow(window1.get(), SnapPosition::kPrimary);
  verify("9. Number of displays in split view changed from 0 to 1", 2, 2);
  split_view_controller2->SnapWindow(window3.get(), SnapPosition::kPrimary);
  verify("10. Number of displays in split view changed from 1 to 2", 3, 2);
  split_view_controller3->SnapWindow(window5.get(), SnapPosition::kPrimary);
  verify("11. Number of displays in split view changed from 2 to 3", 3, 2);
  // For good test coverage, after multi-display split view started with
  // |split_view_controller2|, now we end split view on |split_view_controller2|
  // first, and then end multi-display split view with |split_view_controller3|.
  window3.reset();
  verify("12. Number of displays in split view changed from 3 to 2", 3, 2);
  window5.reset();
  verify("13. Number of displays in split view changed from 2 to 1", 3, 3);
  window1.reset();
  verify("14. Number of displays in split view changed from 1 to 0", 3, 3);
  split_view_controller1->SnapWindow(window2.get(), SnapPosition::kPrimary);
  verify("15. Number of displays in split view changed from 0 to 1", 3, 3);
  // In this case, multi-display split view ends as soon as it starts. The
  // metrics should report that as starting and ending multi-display split view.
  split_view_controller2->SnapWindow(window4.get(), SnapPosition::kPrimary);
  verify(
      "16. Multi-display split view started by snapping last overview window",
      4, 4);
}

// Verify that |SplitViewController::CanSnapWindow| checks that the minimum size
// of the window fits into the left or top, with the default divider position.
// (If the work area length is odd, then the right or bottom will be one pixel
// larger.)
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       SnapWindowWithMinimumSizeTest) {
  UpdateDisplay("800x600,750x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  // It should make no difference which root window has the window passed to
  // |SplitViewController::CanSnapWindow|. What should matter is the root window
  // of the |SplitViewController|. To verify, we test with |bounds_within_root1|
  // and |bounds_within_root2|, and expect the same results.
  for (const gfx::Rect& bounds : {bounds_within_root1, bounds_within_root2}) {
    SCOPED_TRACE(bounds.ToString());
    aura::test::TestWindowDelegate* delegate =
        aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
    std::unique_ptr<aura::Window> window(
        CreateTestWindowInShellWithDelegate(delegate, /*id=*/-1, bounds));
    // Before setting a minimum size, expect that |window| can be snapped in
    // split view on either root window.
    EXPECT_TRUE(SplitViewController::Get(root_windows[0])
                    ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
    EXPECT_TRUE(SplitViewController::Get(root_windows[1])
                    ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
    // Either root window can accommodate a minimum size < 1/2 of the
    // work area width.
    delegate->set_minimum_size(gfx::Size(375, 0));
    EXPECT_TRUE(SplitViewController::Get(root_windows[0])
                    ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
    EXPECT_TRUE(SplitViewController::Get(root_windows[1])
                    ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
    // Only the first root window can accommodate a minimum size 396 wide.
    delegate->set_minimum_size(gfx::Size(396, 0));
    EXPECT_TRUE(SplitViewController::Get(root_windows[0])
                    ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
    EXPECT_FALSE(
        SplitViewController::Get(root_windows[1])
            ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
    // Neither root window can accommodate a minimum size > 1/2 of the work area
    // width.
    delegate->set_minimum_size(gfx::Size(401, 0));
    EXPECT_FALSE(
        SplitViewController::Get(root_windows[0])
            ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
    EXPECT_FALSE(
        SplitViewController::Get(root_windows[1])
            ->CanSnapWindow(window.get(), chromeos::kDefaultSnapRatio));
  }
}

// Verify that when in overview mode, the selector items unsnappable indicator
// shows up when expected.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       OverviewUnsnappableIndicatorVisibility) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window3 =
      CreateUnsnappableWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds_within_root2);
  std::unique_ptr<aura::Window> window6 =
      CreateUnsnappableWindow(bounds_within_root2);
  ToggleOverview();
  auto* item2 = GetOverviewItemForWindow(window2.get());
  auto* item3 = GetOverviewItemForWindow(window3.get());
  auto* item5 = GetOverviewItemForWindow(window5.get());
  auto* item6 = GetOverviewItemForWindow(window6.get());

  // Note: |cannot_snap_label_view_| and its parent will be created on demand.
  ASSERT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  ASSERT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode());
  EXPECT_FALSE(GetCannotSnapWidget(item2));
  EXPECT_FALSE(GetCannotSnapWidget(item3));
  EXPECT_FALSE(GetCannotSnapWidget(item5));
  EXPECT_FALSE(GetCannotSnapWidget(item6));

  SplitViewController::Get(root_windows[0])
      ->SnapWindow(window1.get(), SnapPosition::kPrimary);
  ASSERT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  ASSERT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode());
  EXPECT_FALSE(GetCannotSnapWidget(item2));
  ASSERT_TRUE(GetCannotSnapWidget(item3));
  ui::Layer* item3_unsnappable_layer = GetCannotSnapWidget(item3)->GetLayer();
  EXPECT_EQ(1.f, item3_unsnappable_layer->opacity());
  EXPECT_FALSE(GetCannotSnapWidget(item5));
  EXPECT_FALSE(GetCannotSnapWidget(item6));

  SplitViewController::Get(root_windows[1])
      ->SnapWindow(window4.get(), SnapPosition::kPrimary);
  ASSERT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  ASSERT_TRUE(SplitViewController::Get(root_windows[1])->InSplitViewMode());
  EXPECT_FALSE(GetCannotSnapWidget(item2));
  EXPECT_EQ(1.f, item3_unsnappable_layer->opacity());
  EXPECT_FALSE(GetCannotSnapWidget(item5));
  ASSERT_TRUE(GetCannotSnapWidget(item6));
  ui::Layer* item6_unsnappable_layer = GetCannotSnapWidget(item6)->GetLayer();
  EXPECT_EQ(1.f, item6_unsnappable_layer->opacity());

  SplitViewController::Get(root_windows[0])->EndSplitView();
  ASSERT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  ASSERT_TRUE(SplitViewController::Get(root_windows[1])->InSplitViewMode());
  EXPECT_FALSE(GetCannotSnapWidget(item2));
  EXPECT_EQ(0.f, item3_unsnappable_layer->opacity());
  EXPECT_FALSE(GetCannotSnapWidget(item5));
  EXPECT_EQ(1.f, item6_unsnappable_layer->opacity());

  SplitViewController::Get(root_windows[1])->EndSplitView();
  ASSERT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  ASSERT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode());
  EXPECT_FALSE(GetCannotSnapWidget(item2));
  EXPECT_EQ(0.f, item3_unsnappable_layer->opacity());
  EXPECT_FALSE(GetCannotSnapWidget(item5));
  EXPECT_EQ(0.f, item6_unsnappable_layer->opacity());
}

// Test that enabling the docked magnifier ends clamshell split view on all
// displays.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       DockedMagnifierEndsClamshellSplitView) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  const gfx::Rect bounds_within_root1(0, 0, 400, 400);
  const gfx::Rect bounds_within_root2(800, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2);
  ToggleOverview();
  SplitViewController::Get(root_windows[0])
      ->SnapWindow(window1.get(), SnapPosition::kPrimary);
  SplitViewController::Get(root_windows[1])
      ->SnapWindow(window3.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(InOverviewSession());
  EXPECT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  EXPECT_TRUE(SplitViewController::Get(root_windows[1])->InSplitViewMode());
  Shell::Get()->docked_magnifier_controller()->SetEnabled(true);
  EXPECT_FALSE(InOverviewSession());
  EXPECT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode());
  EXPECT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode());
}

// Tests the no windows widget does not show in faster split screen setup.
TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly,
       NoWindowsWidget) {
  UpdateDisplay("800x600,800x600");
  const aura::Window::Windows root_windows = Shell::GetAllRootWindows();

  // Enter overview normally. Test we show the no windows widget.
  ToggleOverview();
  EXPECT_TRUE(GetOverviewGridForRoot(root_windows[0])->no_windows_widget());
  EXPECT_TRUE(GetOverviewGridForRoot(root_windows[1])->no_windows_widget());
  ToggleOverview();

  // Test faster splitscreen setup with 1 window: Snapping the only window won't
  // start partial overview so no widget.
  std::unique_ptr<aura::Window> w1(CreateAppWindow(gfx::Rect(0, 0, 200, 200)));
  SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
                    chromeos::kDefaultSnapRatio);
  VerifyNotSplitViewOrOverviewSession(w1.get());

  // Test overview -> drag to snap setup with 1 window: Overview will end so no
  // widget.
  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());
  DragWindowTo(GetOverviewItemForWindow(w1.get()), gfx::PointF(0, 0));
  auto* window_state = WindowState::Get(w1.get());
  ASSERT_EQ(chromeos::WindowStateType::kPrimarySnapped,
            window_state->GetStateType());
  VerifyNotSplitViewOrOverviewSession(w1.get());

  // Create 2 windows on the first display, then snap to start partial overview.
  std::unique_ptr<aura::Window> w2(CreateAppWindow(gfx::Rect(0, 0, 200, 200)));

  // Test faster splitscreen setup with 2 windows.
  SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
                    chromeos::kDefaultSnapRatio);
  VerifySplitViewOverviewSession(w1.get());

  // TODO(b/313505530): Determine when to show the widget.
  EXPECT_FALSE(GetOverviewGridForRoot(root_windows[0])->no_windows_widget());
  EXPECT_FALSE(GetOverviewGridForRoot(root_windows[1])->no_windows_widget());

  // Exit partial overview and enter full overview.
  ToggleOverview();
  VerifyNotSplitViewOrOverviewSession(w1.get());
  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());

  // Test overview -> drag to snap setup with 2 windows.
  DragWindowTo(GetOverviewItemForWindow(w1.get()), gfx::PointF(0, 0));
  ASSERT_EQ(chromeos::WindowStateType::kPrimarySnapped,
            window_state->GetStateType());
  VerifySplitViewOverviewSession(w1.get());

  // TODO(b/313505530): Determine when to show the widget.
  EXPECT_FALSE(GetOverviewGridForRoot(root_windows[0])->no_windows_widget());
  EXPECT_FALSE(GetOverviewGridForRoot(root_windows[1])->no_windows_widget());
}

// -----------------------------------------------------------------------------
// OverviewWallpaperTest:

// Test fixture to validate wallpaper changes within overview, including clip,
// rounded corners, and the wallpaper underlay, which is a themed solid color
// layer stacked below the wallpaper.
class OverviewWallpaperTest : public OverviewTestBase {
 public:
  OverviewWallpaperTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kForestFeature, features::kSnapGroup,
                              features::kOsSettingsRevampWayfinding,
                              features::kDeskBarWindowOcclusionOptimization},
        /*disabled_features=*/{});
  }
  OverviewWallpaperTest(const OverviewWallpaperTest&) = delete;
  OverviewWallpaperTest& operator=(const OverviewWallpaperTest&) = delete;
  ~OverviewWallpaperTest() override = default;

  gfx::Rect GetDisplayBoundsForRootWindow(aura::Window* root_window) {
    return display::Screen::GetScreen()
        ->GetDisplayNearestWindow(root_window)
        .bounds();
  }

  ui::Layer* GetWallpaperViewLayer() {
    return Shell::GetPrimaryRootWindowController()
        ->wallpaper_widget_controller()
        ->wallpaper_view()
        ->layer();
  }

  // Test that:
  // Upon Entering Overview:
  // -Wallpaper view layer should be clipped across all displays;
  // - Wallpaper underlay layer should be visible across all displays.
  // Upon Exiting Overview:
  // - Wallpaper view layer should be restored across all displays;
  // - Wallpaper underlay layer should not be visible across all displays.
  void VerifyLayersBoundsOnAllDisplays(bool in_overview) {
    for (auto root : Shell::GetAllRootWindows()) {
      auto* wallpaper_widget_controller =
          RootWindowController::ForWindow(root)->wallpaper_widget_controller();
      auto* wallpaper_view_layer =
          wallpaper_widget_controller->wallpaper_view()->layer();
      auto* wallpaper_underlay_layer =
          wallpaper_widget_controller->wallpaper_underlay_layer();
      EXPECT_EQ(root->bounds(), wallpaper_underlay_layer->bounds());
      EXPECT_EQ(in_overview, wallpaper_underlay_layer->IsVisible());
      EXPECT_EQ(in_overview, !wallpaper_view_layer->clip_rect().IsEmpty());
    }
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Test that the wallpaper layer's clipping (with rounded corners) is applied
// correctly during overview sessions, restores fully upon exit, and that the
// wallpaper underlay layer's visibility refreshes properly upon entering and
// exiting overview.
TEST_F(OverviewWallpaperTest, WallpaperClipRectAndRoundedCorners) {
  const gfx::Rect display_bounds(
      GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));
  auto* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  auto* wallpaper_view_layer =
      wallpaper_widget_controller->wallpaper_view()->layer();
  auto* wallpaper_underlay_layer =
      wallpaper_widget_controller->wallpaper_underlay_layer();
  EXPECT_FALSE(wallpaper_underlay_layer->IsVisible());
  EXPECT_EQ(display_bounds, wallpaper_view_layer->bounds());

  // Ensure the wallpaper begins with its original dimensions (matching the
  // active display) and has square corners.
  EXPECT_EQ(display_bounds, wallpaper_view_layer->bounds());
  EXPECT_TRUE(wallpaper_view_layer->clip_rect().IsEmpty());
  EXPECT_TRUE(wallpaper_view_layer->rounded_corner_radii().IsEmpty());

  // Enter overview mode and verify that the wallpaper is clipped with rounded
  // corners.
  ToggleOverview();
  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  EXPECT_EQ(overview_grid->GetWallpaperClipBounds(),
            wallpaper_view_layer->clip_rect());
  EXPECT_EQ(kWallpaperClipRoundedCornerRadii,
            wallpaper_view_layer->rounded_corner_radii());

  EXPECT_TRUE(wallpaper_underlay_layer->IsVisible());

  // Exit overview. Check that the wallpaper has been fully restored and the
  // wallpaper underlay layer becomes invisible.
  ToggleOverview();
  EXPECT_EQ(display_bounds, wallpaper_view_layer->bounds());
  EXPECT_TRUE(wallpaper_view_layer->clip_rect().IsEmpty());
  EXPECT_TRUE(wallpaper_view_layer->rounded_corner_radii().IsEmpty());

  EXPECT_FALSE(wallpaper_underlay_layer->IsVisible());
}

// Tests that the wallpaper clipping and wallpaper underlay layer refresh their
// bounds appropriately on display change.
TEST_F(OverviewWallpaperTest, DisplayChange) {
  auto* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  auto* wallpaper_view_layer =
      wallpaper_widget_controller->wallpaper_view()->layer();
  auto* wallpaper_underlay_layer =
      wallpaper_widget_controller->wallpaper_underlay_layer();

  UpdateDisplay("800x600");
  const gfx::Rect display_bounds1(
      GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));

  EXPECT_EQ(display_bounds1, wallpaper_underlay_layer->bounds());
  EXPECT_EQ(display_bounds1, wallpaper_view_layer->bounds());

  UpdateDisplay("1200x900");
  const gfx::Rect display_bounds2(
      GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));
  EXPECT_EQ(display_bounds2, wallpaper_underlay_layer->bounds());
  EXPECT_EQ(display_bounds2, wallpaper_view_layer->bounds());

  ToggleOverview();
  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  EXPECT_EQ(display_bounds2, wallpaper_underlay_layer->bounds());
  EXPECT_EQ(overview_grid->GetWallpaperClipBounds(),
            wallpaper_view_layer->clip_rect());
  EXPECT_TRUE(wallpaper_underlay_layer->IsVisible());

  UpdateDisplay("800x600");
  const gfx::Rect display_bounds3(
      GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));
  EXPECT_EQ(display_bounds3, wallpaper_underlay_layer->bounds());
  EXPECT_EQ(display_bounds3, wallpaper_view_layer->bounds());
}

// Tests that when rotating display, the bounds of the clip wallpaper will be
// adjusted properly.
TEST_F(OverviewWallpaperTest, DisplayRotation) {
  UpdateDisplay("900x600");
  auto* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  auto* wallpaper_view_layer =
      wallpaper_widget_controller->wallpaper_view()->layer();
  auto* wallpaper_underlay_layer =
      wallpaper_widget_controller->wallpaper_underlay_layer();
  auto* display_manager = Shell::Get()->display_manager();

  for (auto rotation :
       {display::Display::ROTATE_270, display::Display::ROTATE_180,
        display::Display::ROTATE_90, display::Display::ROTATE_0}) {
    display_manager->SetDisplayRotation(
        WindowTreeHostManager::GetPrimaryDisplayId(), rotation,
        display::Display::RotationSource::USER);
    const gfx::Rect display_bounds(
        GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));
    EXPECT_EQ(display_bounds, wallpaper_underlay_layer->bounds());
    EXPECT_TRUE(display_bounds.Contains(wallpaper_view_layer->bounds()));
  }
}

// Verifies that wallpaper clipping and underlay layer visibility update
// properly on multiple displays during overview transitions.
TEST_F(OverviewWallpaperTest, MultiDisplayTest) {
  UpdateDisplay("800x700,801+0-800x700,1602+0-800x700");
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  ASSERT_EQ(3U, display_manager->GetNumDisplays());

  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());
  VerifyLayersBoundsOnAllDisplays(/*in_overview=*/true);

  ToggleOverview();
  ASSERT_FALSE(IsInOverviewSession());
  VerifyLayersBoundsOnAllDisplays(/*in_overview=*/false);
}

// Tests that wallpaper clip rect updates properly on all displays on overview
// grid effective bounds change (e.g., virtual desktop bar state changes).
TEST_F(OverviewWallpaperTest, WallpaperClipRefreshWithMultiDisplay) {
  UpdateDisplay("800x700,801+0-800x700,1602+0-800x700");
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  ASSERT_EQ(3U, display_manager->GetNumDisplays());

  ToggleOverview();

  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  auto* desks_bar_view = overview_grid->desks_bar_view();

  // The virtual desks bar is at zero state initially.
  EXPECT_EQ(DeskBarViewBase::State::kZero, desks_bar_view->state());
  SCOPED_TRACE("Desks bar at zero state");
  VerifyLayersBoundsOnAllDisplays(/*in_overview=*/true);

  // Upon expanding the virtual desks bar to state 'kExpanded' by creating a new
  // desk, the wallpaper clip rect bounds will be refreshed.
  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
  EXPECT_EQ(DeskBarViewBase::State::kExpanded, desks_bar_view->state());
  SCOPED_TRACE("Desks bar at expanded state");
  VerifyLayersBoundsOnAllDisplays(/*in_overview=*/true);

  ToggleOverview();
  ASSERT_FALSE(IsInOverviewSession());
  SCOPED_TRACE("Overview exit");
  VerifyLayersBoundsOnAllDisplays(/*in_overview=*/false);
}

// Tests that the wallpaper is clipped in partial overview mode and adjusts
// correctly when the snapped window is resized.
TEST_F(OverviewWallpaperTest, PartialOverviewVisualsAndResize) {
  const gfx::Rect display_bounds(
      GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));
  auto* wallpaper_view_layer = GetWallpaperViewLayer();
  std::unique_ptr<aura::Window> win1(
      CreateAppWindow(gfx::Rect(10, 10, 100, 100)));
  std::unique_ptr<aura::Window> win2(
      CreateAppWindow(gfx::Rect(500, 10, 200, 200)));
  // Check the wallpaper's original state before initiating partial overview.
  EXPECT_EQ(display_bounds, wallpaper_view_layer->bounds());
  EXPECT_TRUE(wallpaper_view_layer->clip_rect().IsEmpty());
  EXPECT_TRUE(wallpaper_view_layer->rounded_corner_radii().IsEmpty());

  // Snap one test window to start partial overview.
  SnapOneTestWindow(win1.get(), chromeos::WindowStateType::kPrimarySnapped,
                    chromeos::kDefaultSnapRatio,
                    WindowSnapActionSource::kSnapByWindowLayoutMenu);
  ASSERT_TRUE(IsInOverviewSession());
  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  // Verify that wallpaper is clipped properly with rounded corners applied in
  // partial overview.
  EXPECT_EQ(overview_grid->GetWallpaperClipBounds(),
            wallpaper_view_layer->clip_rect());
  EXPECT_EQ(kWallpaperClipRoundedCornerRadii,
            wallpaper_view_layer->rounded_corner_radii());

  // Verify that the wallpaper's clip rect updates responsively when the snapped
  // window is dragged in partial overview.
  auto* event_generator = GetEventGenerator();
  const gfx::Point drag_starting_point =
      win1.get()->GetBoundsInScreen().right_center();
  event_generator->set_current_screen_location(drag_starting_point);
  event_generator->PressLeftButton();
  event_generator->MoveMouseBy(-10, 0);
  ASSERT_TRUE(IsInOverviewSession());
  EXPECT_TRUE(WindowState::Get(win1.get())->is_dragged());
  EXPECT_EQ(overview_grid->GetWallpaperClipBounds(),
            wallpaper_view_layer->clip_rect());
  EXPECT_EQ(kWallpaperClipRoundedCornerRadii,
            wallpaper_view_layer->rounded_corner_radii());
  event_generator->ReleaseLeftButton();
}

// Tests that snapping a window in full Overview hides desks widgets; closing
// the window restores full Overview and shows the desks widgets again.
TEST_F(OverviewWallpaperTest, HideDesksWidgetInPartialOverview) {
  std::unique_ptr<aura::Window> win1(
      CreateAppWindow(gfx::Rect(10, 10, 200, 100)));
  std::unique_ptr<aura::Window> win2(
      CreateAppWindow(gfx::Rect(20, 20, 500, 200)));

  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());
  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  auto* desks_widget = overview_grid->desks_widget();
  ASSERT_TRUE(desks_widget->IsVisible());

  SnapOneTestWindow(win2.get(), chromeos::WindowStateType::kSecondarySnapped,
                    chromeos::kTwoThirdSnapRatio,
                    WindowSnapActionSource::kSnapByWindowLayoutMenu);
  ASSERT_TRUE(IsInOverviewSession());
  EXPECT_FALSE(desks_widget->IsVisible());

  // Verify that the desks bar remain invisible in tablet partial overview mode.
  EnterTabletMode();
  ASSERT_TRUE(IsInOverviewSession());
  EXPECT_FALSE(desks_widget->IsVisible());

  LeaveTabletMode();
  ASSERT_TRUE(IsInOverviewSession());
  EXPECT_FALSE(desks_widget->IsVisible());

  // Closing `w2` in partial overview restores full Overview mode, making desks
  // widgets visible again.
  win2.reset();
  ASSERT_TRUE(IsInOverviewSession());
  EXPECT_TRUE(desks_widget->IsVisible());
}

// Tests the no windows widget doesn't show during dragging to partial overview.
// Regression test for http://b/313505530.
TEST_F(OverviewWallpaperTest, NoWindowsWidget) {
  UpdateDisplay("800x600,800x600");
  const aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);

  // Enter full overview with no windows. Test we show the no windows widget.
  ToggleOverview();
  EXPECT_TRUE(GetOverviewGridForRoot(root_windows[0])->no_windows_widget());
  EXPECT_TRUE(GetOverviewGridForRoot(root_windows[1])->no_windows_widget());
  ToggleOverview();

  // Enter full overview with windows only on display 1.
  std::unique_ptr<aura::Window> w1(CreateAppWindow(gfx::Rect(0, 0, 200, 200)));
  std::unique_ptr<aura::Window> w2(CreateAppWindow(gfx::Rect(0, 0, 200, 200)));
  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());
  // TODO(b/313505530): Determine whether to show the widget.
  auto* grid0 = GetOverviewGridForRoot(root_windows[0]);
  auto* grid1 = GetOverviewGridForRoot(root_windows[1]);
  EXPECT_FALSE(grid0->no_windows_widget());
  EXPECT_FALSE(grid1->no_windows_widget());
  ASSERT_TRUE(grid0->desks_widget()->IsVisible());
  ASSERT_TRUE(grid1->desks_widget()->IsVisible());

  // Start dragging. Test we don't show the widget.
  auto* event_generator = GetEventGenerator();
  auto* overview_item = GetOverviewItemForWindow(w1.get());
  DragItemToPoint(overview_item, gfx::Point(0, 0), event_generator,
                  /*by_touch_gestures=*/false, /*drop=*/false);
  EXPECT_FALSE(grid0->no_windows_widget());
  EXPECT_FALSE(grid1->no_windows_widget());

  // Release the drag. Test we don't show the widget.
  event_generator->ReleaseLeftButton();
  ASSERT_EQ(WindowStateType::kPrimarySnapped,
            WindowState::Get(w1.get())->GetStateType());
  EXPECT_FALSE(grid0->no_windows_widget());
  EXPECT_FALSE(grid1->no_windows_widget());

  // Test the split view UI is on display 1 but not display 2, with no toast on
  // display 2.
  VerifySplitViewOverviewSession(w1.get());
  EXPECT_FALSE(grid1->GetSplitViewSetupView());
}

// Tests that the wallpaper view layer clips correctly with animation upon
// entering Overview mode and that both the wallpaper view layer and underlay
// layer restore properly upon exiting.
TEST_F(OverviewWallpaperTest, WallpaperClipAnimation) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  const gfx::Rect display_bounds(
      GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));

  auto* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  auto* wallpaper_view_layer =
      wallpaper_widget_controller->wallpaper_view()->layer();
  auto* wallpaper_underlay_layer =
      wallpaper_widget_controller->wallpaper_underlay_layer();
  EXPECT_FALSE(wallpaper_underlay_layer->IsVisible());

  ui::LayerAnimator* wallpaper_view_layer_animator =
      wallpaper_view_layer->GetAnimator();
  ASSERT_FALSE(wallpaper_view_layer_animator->is_animating());
  ASSERT_EQ(display_bounds, wallpaper_view_layer->bounds());

  ToggleOverview();
  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  const auto& wallpaper_clip_bounds = overview_grid->GetWallpaperClipBounds();
  EXPECT_TRUE(wallpaper_view_layer_animator->is_animating());
  EXPECT_TRUE(
      wallpaper_view_layer->clip_rect().Contains(wallpaper_clip_bounds));
  EXPECT_TRUE(display_bounds.Contains(wallpaper_view_layer->clip_rect()));

  ui::LayerAnimationStoppedWaiter layer_animation_stopped_waiter;
  layer_animation_stopped_waiter.Wait(wallpaper_view_layer);
  EXPECT_TRUE(wallpaper_underlay_layer->IsVisible());
  EXPECT_FALSE(wallpaper_view_layer_animator->is_animating());
  EXPECT_EQ(wallpaper_view_layer->clip_rect(), wallpaper_clip_bounds);

  ToggleOverview();
  EXPECT_TRUE(wallpaper_view_layer_animator->is_animating());
  EXPECT_TRUE(display_bounds.Contains(wallpaper_view_layer->clip_rect()));

  layer_animation_stopped_waiter.Wait(wallpaper_view_layer);
  EXPECT_FALSE(wallpaper_view_layer_animator->is_animating());
  ASSERT_EQ(display_bounds, wallpaper_view_layer->bounds());
  EXPECT_FALSE(wallpaper_underlay_layer->IsVisible());
  EXPECT_TRUE(wallpaper_view_layer->clip_rect().IsEmpty());
}

// Tests that we skip the wallpaper clipping when there is a maximized window.
TEST_F(OverviewWallpaperTest, NoAnimationWithMaximizedWindow) {
  std::unique_ptr<aura::Window> window1(CreateAppWindow());
  const WMEvent maximize_event(WM_EVENT_MAXIMIZE);
  WindowState::Get(window1.get())->OnWMEvent(&maximize_event);
  ASSERT_TRUE(WindowState::Get(window1.get())->IsMaximized());

  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // The wallpaper is completely occluded by the maximized window + shelf here,
  // so we can optimize and skip the animation.
  ToggleOverview();
  ui::Layer* wallpaper_view_layer = GetWallpaperViewLayer();
  ui::LayerAnimator* animator = wallpaper_view_layer->GetAnimator();
  EXPECT_FALSE(animator->is_animating());

  WaitForOverviewEnterAnimation();
  VerifyLayersBoundsOnAllDisplays(/*in_overview=*/true);

  // The wallpaper is visible in overview, so it has to animate.
  ToggleOverview();
  EXPECT_TRUE(animator->is_animating());
  WaitForOverviewExitAnimation();

  // The wallpaper is not tracked as an overview exit animation. Ensure it's
  // animation is complete here. This is a no-op if the wallpaper animation
  // completes before the overview exit animations.
  ui::LayerAnimationStoppedWaiter().Wait(wallpaper_view_layer);
  VerifyLayersBoundsOnAllDisplays(/*in_overview=*/false);
}

// Tests that the shelf's opaque background transitions from visible (default)
// to invisible (overview) and back to visible (overview exit).
TEST_F(OverviewWallpaperTest, ShelfOpaqueBackground) {
  EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
  ShelfWidget* shelf_widget = GetPrimaryShelf()->shelf_widget();
  ui::Layer* opaque_background_layer =
      shelf_widget->GetDelegateViewOpaqueBackgroundLayerForTesting();
  ASSERT_TRUE(opaque_background_layer->IsVisible());

  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());
  EXPECT_FALSE(opaque_background_layer->IsVisible());

  ToggleOverview();
  ASSERT_FALSE(IsInOverviewSession());
  EXPECT_TRUE(opaque_background_layer->IsVisible());
}

// Tests that wallpaper clip rect bounds update upon virtual desk bar state
// changes, and that desk bar background is not configured.
TEST_F(OverviewWallpaperTest, VirtualDesksBarStateChange) {
  const gfx::Rect display_bounds(
      GetDisplayBoundsForRootWindow(Shell::GetPrimaryRootWindow()));
  auto* wallpaper_widget_controller =
      Shell::GetPrimaryRootWindowController()->wallpaper_widget_controller();
  auto* wallpaper_view_layer =
      wallpaper_widget_controller->wallpaper_view()->layer();
  auto* wallpaper_underlay_layer =
      wallpaper_widget_controller->wallpaper_underlay_layer();
  EXPECT_EQ(display_bounds, wallpaper_view_layer->bounds());
  EXPECT_TRUE(wallpaper_view_layer->clip_rect().IsEmpty());
  EXPECT_TRUE(wallpaper_view_layer->rounded_corner_radii().IsEmpty());
  EXPECT_FALSE(wallpaper_underlay_layer->IsVisible());

  ToggleOverview();
  OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get();
  auto* desks_bar_view = overview_grid->desks_bar_view();
  // The virtual desks bar is at zero state initially.
  EXPECT_EQ(DeskBarViewBase::State::kZero, desks_bar_view->state());
  EXPECT_FALSE(desks_bar_view->background_view());
  gfx::Rect wallpaper_clip_rect_for_zero_state(
      wallpaper_view_layer->clip_rect());
  EXPECT_EQ(overview_grid->GetWallpaperClipBounds(),
            wallpaper_clip_rect_for_zero_state);
  EXPECT_EQ(kWallpaperClipRoundedCornerRadii,
            wallpaper_view_layer->rounded_corner_radii());
  EXPECT_TRUE(wallpaper_underlay_layer->IsVisible());

  // Upon expanding the virtual desks bar to state 'kExpanded' by creating a new
  // desk, the wallpaper clip rect bounds will be refreshed.
  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
  EXPECT_EQ(DeskBarViewBase::State::kExpanded, desks_bar_view->state());
  gfx::Rect wallpaper_clip_rect_for_expanded_state(
      wallpaper_view_layer->clip_rect());

  // The updated `wallpaper_clip_rect_for_expanded_state` will be smaller than
  // the previous `wallpaper_clip_rect_for_zero_state`.
  EXPECT_TRUE(wallpaper_clip_rect_for_zero_state.Contains(
      wallpaper_clip_rect_for_expanded_state));
  EXPECT_EQ(overview_grid->GetWallpaperClipBounds(),
            wallpaper_clip_rect_for_expanded_state);
  EXPECT_EQ(kWallpaperClipRoundedCornerRadii,
            wallpaper_view_layer->rounded_corner_radii());
  EXPECT_TRUE(wallpaper_underlay_layer->IsVisible());
}

// Tests that the wallpaper clip does not intersect the desk bar when while
// dragging an item. Regression test for http://b/339882124.
TEST_F(OverviewWallpaperTest, VerticalDeskBar) {
  UpdateDisplay("800x1200");
  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);

  auto window = CreateAppWindow(gfx::Rect(400, 400));

  ToggleOverview();

  // Move the overview item a bit so that the top and bottom snap indicators
  // show up.
  auto* item = GetOverviewItemForWindow(window.get());
  GetEventGenerator()->set_current_screen_location(
      gfx::ToRoundedPoint(item->target_bounds().CenterPoint()));
  GetEventGenerator()->PressLeftButton();
  GetEventGenerator()->MoveMouseBy(10, 10);

  // Test that the desks bar does not intersect the wallpaper clip. The desk bar
  // is in screen bounds while the clip is in parent bounds. However, with one
  // display screen bounds are equivalent to bounds in root window, and since
  // the wallpaper view is the size of the root window, it is also in root
  // window bounds, so a conversion is unnecessary.
  auto* desks_bar_view = GetOverviewSession()->grid_list()[0]->desks_bar_view();
  auto* wallpaper_view_layer = GetWallpaperViewLayer();
  const int desk_bar_bottom = desks_bar_view->GetBoundsInScreen().bottom();
  const int clip_top = wallpaper_view_layer->clip_rect().y();
  // A little overlap is ok since the desk bar has a transparent background.
  EXPECT_NEAR(desk_bar_bottom, clip_top, 30);
}

TEST_F(OverviewWallpaperTest, CenterOverviewItems) {
  auto window1 = CreateAppWindow(gfx::Rect(0, 0, 100, 50));
  auto window2 = CreateAppWindow(gfx::Rect(20, 10, 200, 100));
  auto window3 = CreateAppWindow(gfx::Rect(30, 20, 300, 200));
  auto window4 = CreateAppWindow(gfx::Rect(40, 30, 400, 300));
  auto window5 = CreateAppWindow(gfx::Rect(50, 40, 500, 400));
  auto window6 = CreateAppWindow(gfx::Rect(60, 50, 600, 500));
  auto window7 = CreateAppWindow(gfx::Rect(70, 60, 700, 600));
  auto window8 = CreateAppWindow(gfx::Rect(80, 70, 100, 100));
  auto window9 = CreateAppWindow(gfx::Rect(90, 80, 200, 200));
  auto window10 = CreateAppWindow(gfx::Rect(100, 90, 300, 300));
  auto window11 = CreateAppWindow(gfx::Rect(110, 100, 500, 500));

  ToggleOverview();
  ASSERT_TRUE(IsInOverviewSession());

  const auto* overview_grid =
      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
  ASSERT_TRUE(overview_grid);
  const auto& overview_items = overview_grid->item_list();
  ASSERT_EQ(overview_items.size(), 11u);

  // If the middle of the bounding box which contains the bounds of the overview
  // items is aligned with the middle of the grid, then the overview items are
  // centered.
  gfx::RectF union_bounds;
  for (const auto& overview_item : overview_items) {
    union_bounds.Union(overview_item->target_bounds());
  }

  EXPECT_NEAR(overview_grid->GetGridEffectiveBounds().CenterPoint().x(),
              union_bounds.CenterPoint().x(), 1);
}

// Tests that the drop target bounds are configured to match the overview item
// it is a placeholder for with the center overview items processing. See
// regression at http://b/330386194.
TEST_F(OverviewWallpaperTest, DropTargetBounds) {
  // Pre-add a desk to prevent the desks bar from expanding when dragging
  // starts.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());

  auto window0 = CreateAppWindow(gfx::Rect(10, 10, 200, 100));
  auto window1 = CreateAppWindow(gfx::Rect(20, 20, 300, 200));
  auto window2 = CreateAppWindow(gfx::Rect(30, 30, 220, 110));
  auto window3 = CreateAppWindow(gfx::Rect(30, 20, 300, 200));
  auto window4 = CreateAppWindow(gfx::Rect(40, 30, 400, 300));
  auto window5 = CreateAppWindow(gfx::Rect(50, 40, 500, 400));

  OverviewController* overview_controller = OverviewController::Get();
  overview_controller->StartOverview(OverviewStartAction::kTests,
                                     OverviewEnterExitType::kImmediateEnter);
  ASSERT_TRUE(overview_controller->InOverviewSession());

  aura::Window* primary_root_window = Shell::GetPrimaryRootWindow();
  auto* overview_grid = GetOverviewGridForRoot(primary_root_window);
  ASSERT_TRUE(overview_grid);
  const auto& item_list = overview_grid->item_list();
  ASSERT_EQ(6u, item_list.size());

  for (const auto& overview_item : item_list) {
    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.get(),
                      primary_root_window->GetBoundsInScreen().CenterPoint(),
                      event_generator, by_touch, /*drop=*/false);
      ASSERT_TRUE(overview_controller->InOverviewSession());

      auto* drop_target = overview_grid->drop_target();
      ASSERT_TRUE(drop_target);

      // Verify that the bounds of the `drop_target` will be the same as the
      // `target_bounds_before_dragging`.
      ASSERT_EQ(gfx::RectF(drop_target->target_bounds()),
                target_bounds_before_dragging);

      if (by_touch) {
        event_generator->ReleaseTouch();
      } else {
        event_generator->ReleaseLeftButton();
      }
    }
  }
}

}  // namespace ash