chromium/ash/wm/gestures/back_gesture/back_gesture_event_handler_unittest.cc

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

#include "ash/wm/gestures/back_gesture/back_gesture_event_handler.h"

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/test/keyboard_test_util.h"
#include "ash/public/cpp/keyboard/keyboard_controller.h"
#include "ash/screen_util.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/hotseat_widget.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/model/virtual_keyboard_model.h"
#include "ash/test/ash_test_base.h"
#include "ash/test_shell_delegate.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_divider.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/workspace/backdrop_controller.h"
#include "ash/wm/workspace/workspace_layout_manager.h"
#include "ash/wm/workspace_controller.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/test_accelerator_target.h"
#include "ui/display/test/display_manager_test_api.h"

namespace ash {

namespace {

void StartKioskSession() {
  SessionInfo info;
  info.is_running_in_app_mode = true;
  info.state = session_manager::SessionState::ACTIVE;
  Shell::Get()->session_controller()->SetSessionInfo(info);
}

}  // namespace

class BackGestureEventHandlerTest : public AshTestBase {
 public:
  // Distance that swiping from left edge to let the affordance achieve
  // activated state.
  static constexpr int kSwipingDistanceForGoingBack = 80;

  explicit BackGestureEventHandlerTest(bool can_go_back = true)
      : can_go_back_(can_go_back) {}
  BackGestureEventHandlerTest(const BackGestureEventHandlerTest&) = delete;
  BackGestureEventHandlerTest& operator=(const BackGestureEventHandlerTest&) =
      delete;
  ~BackGestureEventHandlerTest() override = default;

  void SetUp() override {
    std::unique_ptr<TestShellDelegate> delegate;
    if (!can_go_back_) {
      delegate = std::make_unique<TestShellDelegate>();
      delegate->SetCanGoBack(false);
    }
    AshTestBase::SetUp(std::move(delegate));

    RecreateTopWindow(chromeos::AppType::BROWSER);
    TabletModeControllerTestApi().EnterTabletMode();
  }

  void TearDown() override {
    top_window_.reset();
    AshTestBase::TearDown();
  }

  void RegisterBackPressAndRelease(ui::TestAcceleratorTarget* back_press,
                                   ui::TestAcceleratorTarget* back_release) {
    AcceleratorControllerImpl* controller =
        Shell::Get()->accelerator_controller();

    // Register an accelerator that looks for back presses.
    ui::Accelerator accelerator_back_press(ui::VKEY_BROWSER_BACK, ui::EF_NONE);
    accelerator_back_press.set_key_state(ui::Accelerator::KeyState::PRESSED);
    controller->Register({accelerator_back_press}, back_press);

    // Register an accelerator that looks for back releases.
    ui::Accelerator accelerator_back_release(ui::VKEY_BROWSER_BACK,
                                             ui::EF_NONE);
    accelerator_back_release.set_key_state(ui::Accelerator::KeyState::RELEASED);
    controller->Register({accelerator_back_release}, back_release);
  }

  // Send touch event with |type| to the toplevel window event handler.
  void SendTouchEvent(const gfx::Point& position, ui::EventType type) {
    ui::TouchEvent event =
        ui::TouchEvent(type, position, base::TimeTicks::Now(),
                       ui::PointerDetails(ui::EventPointerType::kTouch,
                                          /*pointer_id=*/5, /*radius_x=*/5.0f,
                                          /*radius_y=*/5.0, /*force=*/1.0f));
    ui::Event::DispatcherApi(&event).set_target(top_window_.get());
    Shell::Get()->back_gesture_event_handler()->OnTouchEvent(&event);
  }

  void RecreateTopWindow(chromeos::AppType app_type) {
    top_window_ = CreateAppWindow(gfx::Rect(), app_type);
  }

  void ResetTopWindow() { top_window_.reset(); }

  // Generates a scroll sequence that will create a back gesture.
  void GenerateBackSequence() {
    GetEventGenerator()->GestureScrollSequence(
        gfx::Point(0, 100), gfx::Point(kSwipingDistanceForGoingBack + 10, 100),
        base::Milliseconds(100), 3);
  }

  TestShellDelegate* GetShellDelegate() {
    return static_cast<TestShellDelegate*>(Shell::Get()->shell_delegate());
  }

  void SendFullscreenEvent(WindowState* window_state) {
    const WMEvent fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
    window_state->OnWMEvent(&fullscreen_event);
  }

  aura::Window* top_window() { return top_window_.get(); }

 private:
  bool can_go_back_;
  std::unique_ptr<aura::Window> top_window_;
};

class BackGestureEventHandlerTestCantGoBack
    : public BackGestureEventHandlerTest {
 public:
  BackGestureEventHandlerTestCantGoBack()
      : BackGestureEventHandlerTest(false) {}
};

TEST_F(BackGestureEventHandlerTest, SwipingFromLeftEdgeToGoBack) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  // Tests that swiping from the left less than |kSwipingDistanceForGoingBack|
  // should not go to previous page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  const gfx::Point start(0, 100);
  generator->GestureScrollSequence(
      start, gfx::Point(kSwipingDistanceForGoingBack - 10, 100),
      base::Milliseconds(100), 3);
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());

  // Tests that swiping from the left more than |kSwipingDistanceForGoingBack|
  // should go to previous page.
  generator->GestureScrollSequence(
      start, gfx::Point(kSwipingDistanceForGoingBack + 10, 100),
      base::Milliseconds(100), 3);
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
}

TEST_F(BackGestureEventHandlerTest, FlingFromLeftEdgeToGoBack) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  // Tests that fling from the left with velocity smaller than
  // |kFlingVelocityForGoingBack| should not go to previous page.
  // Drag further than |touch_slop| in GestureDetector to trigger scroll
  // sequence. Note, |touch_slop| equals to 15.05, which is the value of
  // |max_touch_move_in_pixels_for_click_| + |kSlopEpsilon|. Generate the scroll
  // sequence with short duration and only one step for FLING scroll gestures.
  // X-velocity here will be 800 dips/seconds.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->GestureScrollSequence(gfx::Point(0, 0), gfx::Point(16, 0),
                                   base::Milliseconds(20),
                                   /*steps=*/1);
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());

  // Tests that fling from the left with velocity larger than
  // |kFlingVelocityForGoingBack| should go to previous page. X-velocity here
  // will be 1600 dips/seconds.
  generator->GestureScrollSequence(gfx::Point(0, 0), gfx::Point(16, 0),
                                   base::Milliseconds(1),
                                   /*steps=*/1);
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());

  // Tests that fling from the left with velocity smaller than
  // |kFlingVelocityForGoingBack| but dragged further enough to trigger
  // activated affordance should still go back to previous page. X-velocity here
  // will be 800 dips/seconds and drag distance is 160, which is larger than
  // |kSwipingDistanceForGoingBack|.
  generator->GestureScrollSequence(gfx::Point(0, 0), gfx::Point(160, 0),
                                   base::Milliseconds(200),
                                   /*steps=*/1);
  EXPECT_EQ(2, target_back_press.accelerator_count());
  EXPECT_EQ(2, target_back_release.accelerator_count());
}

TEST_F(BackGestureEventHandlerTestCantGoBack, GoBackInOverviewMode) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  ASSERT_FALSE(WindowState::Get(top_window())->IsMinimized());
  ASSERT_TRUE(window_util::ShouldMinimizeTopWindowOnBack());
  GenerateBackSequence();
  // Should trigger window minimize instead of go back.
  EXPECT_EQ(0, target_back_release.accelerator_count());
  EXPECT_TRUE(WindowState::Get(top_window())->IsMinimized());

  WindowState::Get(top_window())->Unminimize();
  ASSERT_FALSE(WindowState::Get(top_window())->IsMinimized());
  auto* shell = Shell::Get();
  EnterOverview();
  ASSERT_TRUE(shell->overview_controller()->InOverviewSession());
  GenerateBackSequence();
  // Should trigger go back instead of minimize the window since it is in
  // overview mode.
  EXPECT_EQ(1, target_back_release.accelerator_count());

  // Swipe back at overview mode without opened window should still trigger
  // going back.
  ExitOverview();
  ResetTopWindow();
  EnterOverview();
  GenerateBackSequence();
  EXPECT_EQ(2, target_back_release.accelerator_count());
  EXPECT_TRUE(shell->app_list_controller()->IsHomeScreenVisible());
}

TEST_F(BackGestureEventHandlerTest, GoBackInHomeScreenPage) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  auto* shell = Shell::Get();

  // Should not go back if it is not in ACTIVE session.
  ASSERT_FALSE(shell->overview_controller()->InOverviewSession());
  ASSERT_FALSE(shell->app_list_controller()->IsHomeScreenVisible());
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::LOCKED);
  GenerateBackSequence();
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());

  // Reset the top window to make sure the back behavior in home screen is not
  // because of sending back event to the top window.
  ResetTopWindow();
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::ACTIVE);
  shell->app_list_controller()->GoHome(GetPrimaryDisplay().id());
  ASSERT_TRUE(shell->app_list_controller()->IsHomeScreenVisible());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
  GenerateBackSequence();
  // Stay in home screen and none back event will be triggered.
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  GetEventGenerator()->GestureTapAt(GetAppListTestHelper()
                                        ->GetAppListView()
                                        ->search_box_view()
                                        ->GetBoundsInScreen()
                                        .CenterPoint());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);
  GenerateBackSequence();
  // Exit home screen search page and back to |kFullscreenAllApps| state. But
  // this is not triggered by sending back event.
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
}

TEST_F(BackGestureEventHandlerTest, CancelOnScreenRotation) {
  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());
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  // 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);

  gfx::Point start(0, 100);
  gfx::Point update_and_end(200, 100);
  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  // Rotate the screen by 270 degree during drag.
  test_api.SetDisplayRotation(display::Display::ROTATE_270,
                              display::Display::RotationSource::ACTIVE);
  EXPECT_EQ(test_api.GetCurrentOrientation(),
            chromeos::OrientationType::kPortraitPrimary);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Left edge swipe back should be cancelled due to screen rotation, so the
  // fling event with velocity larger than |kFlingVelocityForGoingBack| above
  // will not trigger actual going back.
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
}

// Tests that there is no crash when destroying the window during drag the
// back gesture affordance from the left edge.
TEST_F(BackGestureEventHandlerTest, DestroyWindowDuringDrag) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  gfx::Point start(0, 100);
  gfx::Point update_and_end(200, 100);
  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  ResetTopWindow();
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
}

// Tests back gesture while in split view mode.
TEST_F(BackGestureEventHandlerTest, DragFromSplitViewDivider) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  gfx::Rect display_bounds =
      screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
          window1.get());
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  auto* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);
  ASSERT_TRUE(split_view_controller->InSplitViewMode());
  ASSERT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());

  gfx::Rect divider_bounds =
      split_view_controller->split_view_divider()->GetDividerBoundsInScreen(
          /*is_dragging=*/false);
  ui::test::EventGenerator* generator = GetEventGenerator();
  // Drag from the splitview divider's non-resizable area with larger than
  // |kSwipingDistanceForGoingBack| distance should trigger back gesture. The
  // snapped window should go to previous page and divider's position will not
  // be changed.
  gfx::Point start(divider_bounds.x(), 10);
  gfx::Point end(start.x() + kSwipingDistanceForGoingBack + 10, 10);
  EXPECT_GT(split_view_controller->GetDividerPosition(),
            0.33f * display_bounds.width());
  EXPECT_LE(split_view_controller->GetDividerPosition(),
            0.5f * display_bounds.width());
  generator->GestureScrollSequence(start, end, base::Milliseconds(100), 3);
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
  EXPECT_GT(split_view_controller->GetDividerPosition(),
            0.33f * display_bounds.width());
  EXPECT_LE(split_view_controller->GetDividerPosition(),
            0.5f * display_bounds.width());

  // Drag from the divider's resizable area should trigger splitview resizing.
  // Divider's position will be changed and back gesture should not be
  // triggered.
  start = divider_bounds.CenterPoint();
  end = gfx::Point(0.67f * display_bounds.width(), start.y());
  generator->GestureScrollSequence(start, end, base::Milliseconds(100), 3);
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
  EXPECT_GT(split_view_controller->GetDividerPosition(),
            0.5f * display_bounds.width());
  EXPECT_LE(split_view_controller->GetDividerPosition(),
            0.67f * display_bounds.width());
  split_view_controller->EndSplitView();
}

// Tests that back gesture should always activate the snapped window in split
// view that is underneath the finger in different screen orientations. And that
// the snapped window that is underneath should go back to the previous page.
TEST_F(BackGestureEventHandlerTest, BackGestureInSplitViewMode) {
  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());
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  std::unique_ptr<aura::Window> left_window = CreateTestWindow();
  std::unique_ptr<aura::Window> right_window = CreateTestWindow();

  // Start overview first and then snap window in splitview to make sure
  // window activation order remains the same.
  EnterOverview();
  auto* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(left_window.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(right_window.get(),
                                    SnapPosition::kSecondary);

  // 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);

  ASSERT_EQ(right_window.get(), window_util::GetActiveWindow());
  gfx::Point start(0, 10);
  gfx::Point update_and_end(kSwipingDistanceForGoingBack + 10, 10);
  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the left of the display in LandscapePrimary further than
  // |kSwipingDistanceForGoingBack| should activate the physically left snapped
  // window, which is |left_window| and it should go back to the previous page.
  EXPECT_EQ(left_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());

  gfx::Rect divider_bounds =
      split_view_controller->split_view_divider()->GetDividerBoundsInScreen(
          /*is_dragging=*/false);
  start = gfx::Point(divider_bounds.x(), 10);
  update_and_end =
      gfx::Point(divider_bounds.x() + kSwipingDistanceForGoingBack + 10, 10);
  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the split view divider in LandscapePrimary further than
  // |kSwipingDistanceForGoingBack| should activate the physically right snapped
  // window, which is |right_window| and it should go back to the previous page.
  EXPECT_EQ(right_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(2, target_back_press.accelerator_count());
  EXPECT_EQ(2, target_back_release.accelerator_count());

  // 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);

  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the split view divider in LandscapeSecondary further than
  // |kSwipingDistanceForGoingBack| should activate the physically right snapped
  // window, which is |left_window| and it should go back to the previous page.
  EXPECT_EQ(left_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(3, target_back_press.accelerator_count());
  EXPECT_EQ(3, target_back_release.accelerator_count());

  start = gfx::Point(0, 10);
  update_and_end = gfx::Point(kSwipingDistanceForGoingBack + 10, 10);
  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the left of the display in LandscapeSecondary further than
  // |kSwipingDistanceForGoingBack| should activate the physically left snapped
  // window, which is |right_window| and it should go back to the previous page.
  EXPECT_EQ(right_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(4, target_back_press.accelerator_count());
  EXPECT_EQ(4, target_back_release.accelerator_count());

  // 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);

  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the left of the top half of the display in PortraitPrimary
  // further than |kSwipingDistanceForGoingBack| should activate the physically
  // top snapped window, which is |right_window|, and it should go back to the
  // previous page.
  EXPECT_EQ(left_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(5, target_back_press.accelerator_count());
  EXPECT_EQ(5, target_back_release.accelerator_count());

  divider_bounds =
      split_view_controller->split_view_divider()->GetDividerBoundsInScreen(
          false);
  start = gfx::Point(0, divider_bounds.bottom() + 10);
  update_and_end = gfx::Point(kSwipingDistanceForGoingBack + 10, start.y());
  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the left of the bottom half of the display in PortraitPrimary
  // further than |kSwipingDistanceForGoingBack| should activate the physically
  // bottom snapped window, which is |right_window|, and it should go back to
  // the previous page.
  EXPECT_EQ(right_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(6, target_back_press.accelerator_count());
  EXPECT_EQ(6, target_back_release.accelerator_count());

  // 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);

  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the left of the bottom half of the display in
  // PortraitSecondary further than |kSwipingDistanceForGoingBack| should
  // activate the physically bottom snapped window, which is |left_window|, and
  // it should go back to the previous page.
  EXPECT_EQ(left_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(7, target_back_press.accelerator_count());
  EXPECT_EQ(7, target_back_release.accelerator_count());

  start = gfx::Point(0, 10);
  update_and_end = gfx::Point(kSwipingDistanceForGoingBack + 10, 10);
  SendTouchEvent(start, ui::EventType::kTouchPressed);
  SendTouchEvent(update_and_end, ui::EventType::kTouchMoved);
  SendTouchEvent(update_and_end, ui::EventType::kTouchReleased);
  // Swiping from the left of the top half of the display in PortraitSecondary
  // further than |kSwipingDistanceForGoingBack| should activate the physically
  // top snapped window, which is |right_window| and it should go back to the
  // previous page.
  EXPECT_EQ(right_window.get(), window_util::GetActiveWindow());
  EXPECT_EQ(8, target_back_press.accelerator_count());
  EXPECT_EQ(8, target_back_release.accelerator_count());
}

// Tests the back gesture behavior on a non-ARC fullscreened window.
TEST_F(BackGestureEventHandlerTest, FullscreenedWindow) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  WindowState* window_state = WindowState::Get(top_window());
  SendFullscreenEvent(window_state);
  EXPECT_TRUE(window_state->IsFullscreen());

  GenerateBackSequence();
  // First back gesture should let the window exit fullscreen mode instead of
  // triggering go back.
  EXPECT_FALSE(window_state->IsFullscreen());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());

  GenerateBackSequence();
  // Second back gesture should trigger go back.
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
}

// Tests the back gesture behavior in the Kiosk session.
TEST_F(BackGestureEventHandlerTest, KioskSession) {
  StartKioskSession();

  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  // Make the test window fullscreen to emulate a real Kiosk session, since in
  // the Kiosk session an app window is always fullscreen.
  WindowState* window_state = WindowState::Get(top_window());
  SendFullscreenEvent(window_state);
  EXPECT_TRUE(window_state->IsFullscreen());

  GenerateBackSequence();
  // First back gesture should not let the window exit fullscreen mode, as we do
  // it with a fullscreen window oppened in a user session.
  EXPECT_TRUE(window_state->IsFullscreen());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());

  GenerateBackSequence();
  // Second back gesture should not minimize the window, as we do it with a
  // fullscreen window oppened in a user session.
  EXPECT_FALSE(window_util::ShouldMinimizeTopWindowOnBack());
  EXPECT_TRUE(window_state->IsFullscreen());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
}

// Tests the back gesture behavior on a ARC fullscreened window.
TEST_F(BackGestureEventHandlerTest, ARCFullscreenedWindow) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  RecreateTopWindow(chromeos::AppType::ARC_APP);

  WindowState* window_state = WindowState::Get(top_window());
  SendFullscreenEvent(window_state);
  ASSERT_TRUE(window_state->IsFullscreen());

  auto shelf_visible_hotseat_extended = [this]() -> bool {
    auto* shelf = Shelf::ForWindow(top_window());
    const bool shelf_visible = shelf->GetVisibilityState() == SHELF_VISIBLE;
    const bool hotseat_extended =
        shelf->hotseat_widget()->state() == HotseatState::kExtended;
    return shelf_visible && hotseat_extended;
  };

  GenerateBackSequence();
  // First back gesture should show the shelf instead of triggering go back. The
  // app should remain fullscreened.
  EXPECT_TRUE(window_state->IsFullscreen());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
  EXPECT_TRUE(shelf_visible_hotseat_extended());

  // Tapping on a point on the screen should hide the shelf and hotseat.
  GetEventGenerator()->GestureTapAt(gfx::Point(100, 100));
  EXPECT_FALSE(shelf_visible_hotseat_extended());

  // Send another back gesture to bring up the shelf and hotseat.
  GenerateBackSequence();
  EXPECT_TRUE(window_state->IsFullscreen());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
  EXPECT_TRUE(shelf_visible_hotseat_extended());

  GenerateBackSequence();
  // Second back gesture in a row should trigger go back. Fullscreen will be
  // dependent on how the app choses to handle the back event.
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
}

// Tests the back gesture behavior when a Chrome OS IME is visible.
TEST_F(BackGestureEventHandlerTest, BackGestureWithCrosKeyboardTest) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  KeyboardController* keyboard_controller = KeyboardController::Get();
  keyboard_controller->SetEnableFlag(
      keyboard::KeyboardEnableFlag::kExtensionEnabled);
  // The keyboard needs to be in a loaded state before being shown.
  ASSERT_TRUE(keyboard::test::WaitUntilLoaded());
  keyboard_controller->ShowKeyboard();
  EXPECT_TRUE(keyboard_controller->IsKeyboardVisible());

  GenerateBackSequence();
  // First back gesture should hide the virtual keyboard.
  EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());

  GenerateBackSequence();
  // Second back gesture should trigger go back.
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
}

// Tests that the back gesture works properly on the split view divider bar both
// inside and outside of cros virtual keyboard.
TEST_F(BackGestureEventHandlerTest,
       BackGestureWithCrosKeyboardInSplitViewTest) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  std::unique_ptr<aura::Window> left_window = CreateTestWindow();
  std::unique_ptr<aura::Window> right_window = CreateTestWindow();
  auto* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(left_window.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(right_window.get(),
                                    SnapPosition::kSecondary);
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());

  KeyboardController* keyboard_controller = KeyboardController::Get();
  keyboard_controller->SetEnableFlag(
      keyboard::KeyboardEnableFlag::kExtensionEnabled);
  // The keyboard needs to be in a loaded state before being shown.
  ASSERT_TRUE(keyboard::test::WaitUntilLoaded());
  keyboard_controller->ShowKeyboard();
  EXPECT_TRUE(keyboard_controller->IsKeyboardVisible());

  // Get the keyboard bounds:
  keyboard::KeyboardUIController* keyboard_ui_controller =
      keyboard::KeyboardUIController::Get();
  EXPECT_TRUE(keyboard_ui_controller->IsKeyboardVisible());
  gfx::Rect keyboard_bounds = keyboard_ui_controller->GetVisualBoundsInScreen();

  // Start dragging from a position that is right outside the divider bar bounds
  // and outside the VK bounds.
  gfx::Rect divider_bounds =
      split_view_controller->split_view_divider()->GetDividerBoundsInScreen(
          false);
  gfx::Point start = gfx::Point(divider_bounds.CenterPoint().x(), 10);
  EXPECT_FALSE(keyboard_bounds.Contains(start));
  gfx::Point end =
      gfx::Point(start.x() + kSwipingDistanceForGoingBack + 10, start.y());
  GetEventGenerator()->GestureScrollSequence(start, end,
                                             base::Milliseconds(100), 3);
  // Virtual keyboard should be closed.
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());
  EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());

  // Start dragging from the split view divider bar position that is inside the
  // VK bounds.
  keyboard_controller->ShowKeyboard();
  EXPECT_TRUE(keyboard_controller->IsKeyboardVisible());
  start = gfx::Point(divider_bounds.CenterPoint().x(),
                     keyboard_bounds.CenterPoint().y());
  EXPECT_TRUE(keyboard_bounds.Contains(start));
  end = gfx::Point(start.x() + kSwipingDistanceForGoingBack + 10, start.y());
  GetEventGenerator()->GestureScrollSequence(start, end,
                                             base::Milliseconds(100), 3);
  // Nothing should happen.
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());
  EXPECT_TRUE(keyboard_controller->IsKeyboardVisible());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
}

// Tests the back gesture behavior when an Android IME is visible. Due to the
// way the Android IME is implemented, a lot of this test is fake behavior, but
// it will help catch regressions.
TEST_F(BackGestureEventHandlerTest, BackGestureWithAndroidKeyboardTest) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  WindowState* window_state = WindowState::Get(top_window());
  ASSERT_FALSE(window_state->IsMinimized());

  VirtualKeyboardModel* keyboard =
      Shell::Get()->system_tray_model()->virtual_keyboard();
  ASSERT_TRUE(keyboard);
  // Fakes showing the keyboard.
  keyboard->OnArcInputMethodBoundsChanged(gfx::Rect(400, 400));
  EXPECT_TRUE(keyboard->arc_keyboard_visible());

  // Unfortunately we cannot hook this all the wall up to see if the Android IME
  // is hidden, but we can check that back key events are generated and the top
  // window is not minimized.
  GenerateBackSequence();
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
  EXPECT_FALSE(window_state->IsMinimized());
}

// Tests that the back gesture works properly on the split view divider bar both
// inside and outside of Android virtual keyboard.
TEST_F(BackGestureEventHandlerTest,
       BackGestureWithAndroidKeyboardInSplitViewTest) {
  UpdateDisplay("800x600");
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  std::unique_ptr<aura::Window> left_window = CreateTestWindow();
  std::unique_ptr<aura::Window> right_window = CreateTestWindow();
  auto* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(left_window.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(right_window.get(),
                                    SnapPosition::kSecondary);
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());

  VirtualKeyboardModel* keyboard =
      Shell::Get()->system_tray_model()->virtual_keyboard();
  ASSERT_TRUE(keyboard);
  // Fakes showing the keyboard.
  gfx::Rect keyboard_bounds =
      screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
          left_window.get());
  keyboard_bounds.set_y(keyboard_bounds.bottom() - 200);
  keyboard_bounds.set_height(200);
  keyboard->OnArcInputMethodBoundsChanged(keyboard_bounds);
  EXPECT_TRUE(keyboard->arc_keyboard_visible());

  // Start dragging from the split view divider bar position that is outside the
  // VK bounds.
  gfx::Rect divider_bounds =
      split_view_controller->split_view_divider()->GetDividerBoundsInScreen(
          false);
  gfx::Point start = gfx::Point(divider_bounds.CenterPoint().x(), 10);
  EXPECT_FALSE(keyboard_bounds.Contains(start));
  gfx::Point end =
      gfx::Point(start.x() + kSwipingDistanceForGoingBack + 10, start.y());
  GetEventGenerator()->GestureScrollSequence(start, end,
                                             base::Milliseconds(100), 3);
  // Virtual keyboard should be closed. But Unfortunately we cannot hook
  // this all the wall up to see if the Android IME is hidden, but we can check
  // that back key events are generated and we're still in both snapped split
  // view state.
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());

  // Start dragging from the split view divider bar position that is inside the
  // VK bounds.
  target_back_press.ResetCounts();
  target_back_release.ResetCounts();
  keyboard->OnArcInputMethodBoundsChanged(keyboard_bounds);
  EXPECT_TRUE(keyboard->arc_keyboard_visible());
  start = gfx::Point(divider_bounds.CenterPoint().x(),
                     keyboard_bounds.CenterPoint().y());
  EXPECT_TRUE(keyboard_bounds.Contains(start));
  end = gfx::Point(start.x() + kSwipingDistanceForGoingBack + 10, start.y());
  GetEventGenerator()->GestureScrollSequence(start, end,
                                             base::Milliseconds(100), 3);
  // Nothing should happen.
  EXPECT_EQ(SplitViewController::State::kBothSnapped,
            split_view_controller->state());
  EXPECT_EQ(0, target_back_press.accelerator_count());
  EXPECT_EQ(0, target_back_release.accelerator_count());
}

TEST_F(BackGestureEventHandlerTest, IgnoreSecondFinger) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  const gfx::Point start_point(0, 100);
  const gfx::Point end_point(200, 100);

  // Scenario 1:
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressTouchId(0, std::make_optional(start_point));
  generator->MoveTouch(end_point);
  // Without releasing the first finger, now press and release the second
  // finger.
  generator->PressTouchId(1);
  generator->ReleaseTouchId(1);
  // Then release the first finger. Back should be able to be performed.
  generator->ReleaseTouchId(0);
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());

  // Scenario 2:
  wm::ActivateWindow(top_window());
  generator->PressTouchId(0, std::make_optional(start_point));
  generator->MoveTouch(end_point);
  // Without releasing the first finger, now press the second finger.
  generator->PressTouchId(1);
  // Release the first finger and then the second finger.
  generator->ReleaseTouchId(0);
  generator->ReleaseTouchId(1);
  // Test that back should still be able to be performed.
  EXPECT_EQ(2, target_back_press.accelerator_count());
  EXPECT_EQ(2, target_back_release.accelerator_count());

  // Scenario 3:
  wm::ActivateWindow(top_window());
  GetShellDelegate()->SetShouldWaitForTouchAck(
      /*should_wait_for_touch_ack=*/true);
  generator->PressTouchId(0, std::make_optional(start_point));
  generator->MoveTouch(end_point);
  // Without releasing the first finger, now press and release the second
  // finger.
  generator->PressTouchId(1);
  generator->ReleaseTouchId(1);
  // Then release the first finger. Back should be able to be performed.
  generator->ReleaseTouchId(0);
  EXPECT_EQ(3, target_back_press.accelerator_count());
  EXPECT_EQ(3, target_back_release.accelerator_count());

  // Scenario 4:
  wm::ActivateWindow(top_window());
  generator->PressTouchId(0, std::make_optional(start_point));
  generator->MoveTouch(end_point);
  // Without releasing the first finger, now press the second finger.
  generator->PressTouchId(1);
  // Release the first finger and then the second finger.
  generator->ReleaseTouchId(0);
  generator->ReleaseTouchId(1);
  // Test that back should still be able to be performed.
  EXPECT_EQ(4, target_back_press.accelerator_count());
  EXPECT_EQ(4, target_back_release.accelerator_count());
}

TEST_F(BackGestureEventHandlerTest, CancelledEventOnSecondFinger) {
  ui::TestAcceleratorTarget target_back_press, target_back_release;
  RegisterBackPressAndRelease(&target_back_press, &target_back_release);

  const gfx::Point start_point(0, 100);
  const gfx::Point end_point(200, 100);

  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressTouchId(0, std::make_optional(start_point));
  generator->MoveTouch(end_point);
  // Without releasing the first finger, now press the second finger.
  generator->PressTouchId(1);
  // Then release the first finger. Back should be able to be performed.
  generator->ReleaseTouchId(0);
  EXPECT_EQ(1, target_back_press.accelerator_count());
  EXPECT_EQ(1, target_back_release.accelerator_count());
  generator->ReleaseTouchId(1);
  // Manually dispatch a ui::EventType::kTouchCancelled event to the second
  // finger to simulate what's happending in real world.
  ui::TouchEvent event = ui::TouchEvent(
      ui::EventType::kTouchCancelled, start_point, base::TimeTicks::Now(),
      ui::PointerDetails(ui::EventPointerType::kTouch,
                         /*pointer_id=*/1, /*radius_x=*/5.0f,
                         /*radius_y=*/5.0, /*force=*/1.0f));
  ui::Event::DispatcherApi(&event).set_target(top_window());
  Shell::Get()->back_gesture_event_handler()->OnTouchEvent(&event);

  wm::ActivateWindow(top_window());
  generator->PressTouchId(0, std::make_optional(start_point));
  generator->MoveTouch(end_point);
  generator->ReleaseTouchId(0);
  // Test that back should still be able to be performed.
  EXPECT_EQ(2, target_back_press.accelerator_count());
  EXPECT_EQ(2, target_back_release.accelerator_count());
}

// Tests that swiping on the backdrop to minimize a non-resizable app will not
// cause a crash. Regression test for http://crbug.com/1064618.
TEST_F(BackGestureEventHandlerTestCantGoBack, NonResizableApp) {
  // Make the top window non-resizable and set its bounds so that the backdrop
  // will take the gesture events.
  top_window()->SetProperty(aura::client::kResizeBehaviorKey,
                            aura::client::kResizeBehaviorCanMinimize);

  WindowState* window_state = WindowState::Get(top_window());
  window_state->Restore();
  SetBoundsWMEvent bounds_event(gfx::Rect(200, 100, 300, 300));
  window_state->OnWMEvent(&bounds_event);
  ASSERT_FALSE(window_state->IsMinimized());

  // Check that the backdrop is visible.
  WorkspaceController* workspace_controller =
      GetWorkspaceControllerForContext(top_window());
  WorkspaceLayoutManager* layout_manager =
      workspace_controller->layout_manager();
  BackdropController* backdrop_controller =
      layout_manager->backdrop_controller();
  aura::Window* backdrop_window = backdrop_controller->backdrop_window();
  ASSERT_TRUE(backdrop_window);
  ASSERT_TRUE(backdrop_window->IsVisible());

  // Generate a back seqeuence. There should be no crash.
  GenerateBackSequence();
  EXPECT_TRUE(window_state->IsMinimized());
}

TEST_F(BackGestureEventHandlerTestCantGoBack, NonAppAndSystemApps) {
  RecreateTopWindow(chromeos::AppType::NON_APP);
  GenerateBackSequence();
  EXPECT_TRUE(WindowState::Get(top_window())->IsMinimized());

  RecreateTopWindow(chromeos::AppType::SYSTEM_APP);
  GenerateBackSequence();
  EXPECT_TRUE(WindowState::Get(top_window())->IsMinimized());
}

// Tests that the back gesture will force minimize even non minimizeable apps.
TEST_F(BackGestureEventHandlerTestCantGoBack, NonMinimizeableApp) {
  // Make the top window non minimizeable.
  top_window()->SetProperty(aura::client::kResizeBehaviorKey,
                            aura::client::kResizeBehaviorNone);
  GenerateBackSequence();
  EXPECT_TRUE(WindowState::Get(top_window())->IsMinimized());
}

}  // namespace ash