chromium/ash/shelf/shelf_drag_handle_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/app_list/test/app_list_test_helper.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/controls/contextual_tooltip.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/drag_handle.h"
#include "ash/shelf/drag_window_from_shelf_controller.h"
#include "ash/shelf/drag_window_from_shelf_controller_test_api.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/test/shelf_layout_manager_test_base.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/functional/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

ShelfWidget* GetShelfWidget() {
  return AshTestBase::GetPrimaryShelf()->shelf_widget();
}

ShelfLayoutManager* GetShelfLayoutManager() {
  return AshTestBase::GetPrimaryShelf()->shelf_layout_manager();
}

}  // namespace

// Test base for unit test related to drag handle contextual nudges.
class DragHandleContextualNudgeTest : public ShelfLayoutManagerTestBase {
 public:
  DragHandleContextualNudgeTest() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kHideShelfControlsInTabletMode);
  }
  ~DragHandleContextualNudgeTest() override = default;

  DragHandleContextualNudgeTest(const DragHandleContextualNudgeTest& other) =
      delete;
  DragHandleContextualNudgeTest& operator=(
      const DragHandleContextualNudgeTest& other) = delete;

  // ShelfLayoutManagerTestBase:
  void SetUp() override {
    ShelfLayoutManagerTestBase::SetUp();
    test_clock_.Advance(base::Hours(2));
    contextual_tooltip::OverrideClockForTesting(&test_clock_);
  }
  void TearDown() override {
    contextual_tooltip::ClearClockOverrideForTesting();
    AshTestBase::TearDown();
  }

  base::SimpleTestClock test_clock_;

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

class DragHandleContextualNudgeTestA11yPrefs
    : public DragHandleContextualNudgeTest,
      public ::testing::WithParamInterface<std::string> {};

INSTANTIATE_TEST_SUITE_P(
    All,
    DragHandleContextualNudgeTestA11yPrefs,
    testing::Values(prefs::kAccessibilityAutoclickEnabled,
                    prefs::kAccessibilitySpokenFeedbackEnabled,
                    prefs::kAccessibilitySwitchAccessEnabled));

TEST_F(DragHandleContextualNudgeTest, ShowDragHandleNudgeWithTimer) {
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());

  // The drag handle should be showing but the nudge should not. A timer to show
  // the nudge should be initialized.
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  // Firing the timer should show the drag handle nudge.
  GetShelfWidget()->GetDragHandle()->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
}

TEST_F(DragHandleContextualNudgeTest, HideDragHandleNudgeHiddenOnMinimize) {
  base::HistogramTester histogram_tester;

  // Creates a test window to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());

  // The drag handle and nudge should be showing after the timer fires.
  GetShelfWidget()->GetDragHandle()->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Minimizing the widget should hide the drag handle and nudge.
  widget->Minimize();
  EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
}

// Tests that the drag handle nudge nudge is hidden when closing the widget and
// setting the ShelfBackgroundType to kHomeLauncher.
TEST_F(DragHandleContextualNudgeTest, DragHandleNudgeHiddenOnClose) {
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());

  DragHandle* const drag_handle = GetShelfWidget()->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  // Close the widget.
  widget->CloseWithReason(views::Widget::ClosedReason::kCloseButtonClicked);
  EXPECT_FALSE(drag_handle->GetVisible());
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
}

// Checks that the shelf cannot be auto hidden while animating shelf drag handle
// nudge.
TEST_F(DragHandleContextualNudgeTest,
       HideDragHandleDoesNotInteruptShowNudgeAnimation) {
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());

  ui::ScopedAnimationDurationScaleMode normal_animation_duration(
      ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);
  GetShelfWidget()->GetDragHandle()->MaybeShowDragHandleNudge();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->show_nudge_animation_in_progress());
  GetShelfLayoutManager()->UpdateAutoHideState();
  // Shelf auto hide should not interrupt animations to show drag handle nudge.
  // Showing the nudge while hiding the shelf is not intended behavior.
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->show_nudge_animation_in_progress());
}

// Checks that the drag handle nudge is not shown when entering kInApp with
// shelf autohide turned on.
TEST_F(DragHandleContextualNudgeTest, DragHandleNotShownForAutoHideShelf) {
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->show_nudge_animation_in_progress());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
}

TEST_F(DragHandleContextualNudgeTest, DoNotShowNudgeWithoutDragHandle) {
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());

  // Minimizing the widget should hide the drag handle and nudge.
  widget->Minimize();
  EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
}

TEST_F(DragHandleContextualNudgeTest,
       ContinueShowingDragHandleNudgeOnActiveWidgetChanged) {
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());
  GetShelfWidget()->GetDragHandle()->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Maximizing and showing a different widget should not hide the drag handle
  // or nudge.
  views::Widget* new_widget = CreateTestWidget();
  new_widget->Maximize();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
}

TEST_F(DragHandleContextualNudgeTest, DragHandleNudgeShownInAppShelf) {
  base::HistogramTester histogram_tester;

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  // Drag handle and nudge should not be shown in clamshell mode.
  EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Test that the first time a user transitions into tablet mode with a
  // maximized window will show the drag nudge immedietly. The drag handle nudge
  // should not be visible yet and the timer to show it should be set.
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  EXPECT_TRUE(GetShelfWidget()
                  ->GetDragHandle()
                  ->has_show_drag_handle_timer_for_testing());

  // Firing the timer should show the nudge for the first time. The nudge should
  // remain visible until the shelf state changes so the timer to hide it should
  // not be set.
  GetShelfWidget()->GetDragHandle()->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  EXPECT_FALSE(GetShelfWidget()
                   ->GetDragHandle()
                   ->has_hide_drag_handle_timer_for_testing());

  // Leaving tablet mode should hide the nudge.
  TabletModeControllerTestApi().LeaveTabletMode();
  EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Reentering tablet mode should show the drag handle but the nudge should
  // not. No timer should be set to show the nudge.
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  EXPECT_FALSE(GetShelfWidget()
                   ->GetDragHandle()
                   ->has_show_drag_handle_timer_for_testing());

  // Advance time for more than a day (which should enable the nudge again).
  test_clock_.Advance(base::Hours(25));

  // Reentering tablet mode with a maximized widget should immedietly show the
  // drag handle and set a timer to show the nudge.
  TabletModeControllerTestApi().LeaveTabletMode();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  // Firing the timer should show the nudge.
  EXPECT_TRUE(GetShelfWidget()
                  ->GetDragHandle()
                  ->has_show_drag_handle_timer_for_testing());
  GetShelfWidget()->GetDragHandle()->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  EXPECT_FALSE(GetShelfWidget()
                   ->GetDragHandle()
                   ->has_show_drag_handle_timer_for_testing());
  // On subsequent shows, the nudge should be hidden after a timeout.
  EXPECT_TRUE(GetShelfWidget()
                  ->GetDragHandle()
                  ->has_hide_drag_handle_timer_for_testing());
}

TEST_F(DragHandleContextualNudgeTest, DragHandleNudgeShownOnTap) {
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  EXPECT_TRUE(GetShelfWidget()
                  ->GetDragHandle()
                  ->has_show_drag_handle_timer_for_testing());
  GetShelfWidget()->GetDragHandle()->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Exiting and re-entering tablet should hide the nudge and put the shelf into
  // the default kInApp shelf state.
  TabletModeControllerTestApi().LeaveTabletMode();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Tapping the drag handle should show the drag handle nudge immedietly and
  // the show nudge timer should be set.
  GetEventGenerator()->GestureTapAt(
      GetShelfWidget()->GetDragHandle()->GetBoundsInScreen().CenterPoint());
  EXPECT_FALSE(GetShelfWidget()
                   ->GetDragHandle()
                   ->has_show_drag_handle_timer_for_testing());
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  EXPECT_TRUE(GetShelfWidget()
                  ->GetDragHandle()
                  ->has_hide_drag_handle_timer_for_testing());
}

TEST_F(DragHandleContextualNudgeTest, DragHandleNudgeNotShownForHiddenShelf) {
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  // The shelf is hidden, so the drag handle nudge should not be shown.
  EXPECT_TRUE(drag_handle->GetVisible());
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
  EXPECT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());

  PrefService* const prefs =
      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  // Back gesture nudge should be allowed if the shelf is hidden.
  EXPECT_TRUE(contextual_tooltip::ShouldShowNudge(
      prefs, contextual_tooltip::TooltipType::kBackGesture, nullptr));

  // Swipe up to show the shelf - this should schedule the drag handle nudge.
  SwipeUpOnShelf();

  // Back gesture nudge should be disallowed at this time, given that the drag
  // handle nudge can be shown.
  EXPECT_FALSE(contextual_tooltip::ShouldShowNudge(
      prefs, contextual_tooltip::TooltipType::kBackGesture, nullptr));

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());
}

// Tapping the drag handle nudge when auto hide shelf is enabled should hide the
// drag handle nudge but should not hide the shelf or hotseat.
TEST_F(DragHandleContextualNudgeTest,
       DragHandleNudgeTapDoesNotHideAutoHiddenShelf) {
  // Sets shelf auto hide behavior.
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  // The shelf and drag handle should be hidden and the nudge should not be
  // scheduled because shelf auto hide is set.
  EXPECT_TRUE(GetPrimaryShelf()->GetAutoHideState() ==
              ShelfAutoHideState::SHELF_AUTO_HIDE_HIDDEN);
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
  EXPECT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());

  // Swipe up to show the shelf. This should show the shelf, extend the hotseat,
  // and schedule the drag handle nudge.
  SwipeUpOnShelf();
  EXPECT_TRUE(GetPrimaryShelf()->GetAutoHideState() ==
              ShelfAutoHideState::SHELF_AUTO_HIDE_SHOWN);
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());
  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  // Firing the show timer should create the nudge set the target visibility for
  // animations..
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());
  EXPECT_TRUE(drag_handle->drag_handle_nudge() != nullptr);

  // Tapping the drag handle nudge should hide the nudge but not affect the
  // visibility of the shelf or hotseat.
  GetEventGenerator()->GestureTapAt(
      drag_handle->drag_handle_nudge()->GetBoundsInScreen().CenterPoint());
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
  EXPECT_TRUE(GetPrimaryShelf()->GetAutoHideState() ==
              ShelfAutoHideState::SHELF_AUTO_HIDE_SHOWN);
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());

  // Swiping down on shelf should hide the shelf and hotseat.
  SwipeDownOnShelf();
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());
  EXPECT_TRUE(GetPrimaryShelf()->GetAutoHideState() ==
              ShelfAutoHideState::SHELF_AUTO_HIDE_HIDDEN);

  // Swiping up on shelf should show the shelf and drag handle but not the
  // nudge or hotseat.
  SwipeUpOnShelf();
  EXPECT_TRUE(GetPrimaryShelf()->GetAutoHideState() ==
              ShelfAutoHideState::SHELF_AUTO_HIDE_SHOWN);
  EXPECT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());
  EXPECT_TRUE(drag_handle->drag_handle_nudge() == nullptr);
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());
}

// Tests that drag handle show is canceled when the shelf is hidden while the
// drag handle is scheduled to be shown.
TEST_F(DragHandleContextualNudgeTest, HidingShelfCancelsDragHandleShow) {
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  // The shelf is hidden, so the drag handle nudge should not be shown.
  EXPECT_TRUE(drag_handle->GetVisible());
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
  EXPECT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());

  // Swipe up to show the shelf - this should schedule the drag handle nudge.
  SwipeUpOnShelf();

  EXPECT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());

  // Hide the shelf, and verify the drag handle show is canceled.
  SwipeDownOnShelf();
  EXPECT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());

  PrefService* const prefs =
      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  // Back gesture nudge should be allowed if the shelf is hidden.
  EXPECT_TRUE(contextual_tooltip::ShouldShowNudge(
      prefs, contextual_tooltip::TooltipType::kBackGesture, nullptr));
}

// Tests that the drag handle nudge is not hidden when the user extends the
// hotseat.
TEST_F(DragHandleContextualNudgeTest,
       DragHandleNudgeNotHiddenByExtendingHotseat) {
  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  // Swipe up to extend the hotseat - verify that the drag handle remain
  // visible.
  SwipeUpOnShelf();
  EXPECT_TRUE(drag_handle->GetVisible());
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());
}

// Tests that the drag handle nudge is horizontally centered in screen, and
// drawn above the shelf drag handle, even after display bounds are updated.
TEST_F(DragHandleContextualNudgeTest, DragHandleNudgeBoundsInScreen) {
  UpdateDisplay("675x1200");
  TabletModeControllerTestApi().EnterTabletMode();

  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  EXPECT_TRUE(drag_handle->GetVisible());
  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  // Calculates absolute difference between horizontal margins of |inner| rect
  // within |outer| rect.
  auto margin_diff = [](const gfx::Rect& inner, const gfx::Rect& outer) -> int {
    const int left = inner.x() - outer.x();
    EXPECT_GE(left, 0);

    const int right = outer.right() - inner.right();
    EXPECT_GE(right, 0);

    return std::abs(left - right);
  };

  // Verify that nudge widget is centered in shelf.
  gfx::Rect shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
  gfx::Rect nudge_bounds =
      drag_handle->drag_handle_nudge()->label()->GetBoundsInScreen();
  EXPECT_LE(margin_diff(nudge_bounds, shelf_bounds), 1);

  // Verify that the nudge vertical bounds - within the shelf bounds, and above
  // the drag handle.
  gfx::Rect drag_handle_bounds = drag_handle->GetBoundsInScreen();
  EXPECT_LE(shelf_bounds.y(), nudge_bounds.y());
  EXPECT_LE(nudge_bounds.bottom(), drag_handle_bounds.y());

  // Change the display bounds, and verify the updated drag handle bounds.
  UpdateDisplay("1200x675");
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Verify that nudge widget is centered in shelf.
  shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
  nudge_bounds = drag_handle->drag_handle_nudge()->label()->GetBoundsInScreen();
  EXPECT_LE(margin_diff(nudge_bounds, shelf_bounds), 1);

  // Verify that the nudge vertical bounds - within the shelf bounds, and above
  // the drag handle.
  drag_handle_bounds = drag_handle->GetBoundsInScreen();
  EXPECT_LE(shelf_bounds.y(), nudge_bounds.y());
  EXPECT_LE(nudge_bounds.bottom(), drag_handle_bounds.y());
}

// Tests that drag handle does not hide during the window drag from shelf
// gesture.
TEST_F(DragHandleContextualNudgeTest,
       DragHandleNudgeNotHiddenDuringWindowDragFromShelf) {
  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  TabletModeControllerTestApi().LeaveTabletMode();
  // Advance time for more than a day (which should enable the nudge again).
  test_clock_.Advance(base::Hours(25));
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->has_hide_drag_handle_timer_for_testing());

  const gfx::Point start = drag_handle->GetBoundsInScreen().CenterPoint();
  // Simulates window drag from shelf gesture, and verifies that the timer to
  // hide the drag handle nudge is canceled when the window drag from shelf
  // starts.
  GetEventGenerator()->GestureScrollSequenceWithCallback(
      start, start + gfx::Vector2d(0, -200), base::Milliseconds(50),
      /*num_steps = */ 6,
      base::BindRepeating(
          [](DragHandle* drag_handle, ui::EventType type,
             const gfx::Vector2dF& offset) {
            DragWindowFromShelfController* window_drag_controller =
                GetShelfLayoutManager()->window_drag_controller_for_testing();
            if (window_drag_controller &&
                window_drag_controller->dragged_window()) {
              EXPECT_FALSE(
                  drag_handle->has_hide_drag_handle_timer_for_testing());
            }
            const bool scroll_end = type == ui::EventType::kGestureScrollEnd;
            EXPECT_EQ(!scroll_end,
                      drag_handle->gesture_nudge_target_visibility());
          },
          drag_handle));

  // The nudge should be hidden when the gesture completes.
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
}

// Tests that window drag from shelf cancels drag handle contextual nudge from
// showing.
TEST_F(DragHandleContextualNudgeTest,
       DragHandleNudgeNotShownDuringWindowDragFromShelf) {
  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  EXPECT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());

  const gfx::Point start =
      GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
  // Simulates window drag from shelf gesture, and verifies that the timer to
  // show the drag handle nudge is canceled when the window drag from shelf
  // starts.
  GetEventGenerator()->GestureScrollSequenceWithCallback(
      start, start + gfx::Vector2d(0, -200), base::Milliseconds(50),
      /*num_steps = */ 6,
      base::BindRepeating(
          [](DragHandle* drag_handle, ui::EventType type,
             const gfx::Vector2dF& offset) {
            DragWindowFromShelfController* window_drag_controller =
                GetShelfLayoutManager()->window_drag_controller_for_testing();
            if (window_drag_controller &&
                window_drag_controller->dragged_window()) {
              EXPECT_FALSE(
                  drag_handle->has_show_drag_handle_timer_for_testing());

              // Attempt to schedule the nudge should fail.
              if (type != ui::EventType::kGestureScrollEnd) {
                drag_handle->ScheduleShowDragHandleNudge();
                EXPECT_FALSE(
                    drag_handle->has_show_drag_handle_timer_for_testing());
              }
            }
            EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
          },
          drag_handle));

  // The nudge should be hidden when the gesture completes.
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
}

TEST_F(DragHandleContextualNudgeTest, GestureSwipeHidesDragHandleNudge) {
  base::HistogramTester histogram_tester;

  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  const gfx::Point start = drag_handle->GetBoundsInScreen().CenterPoint();
  // Simulates a swipe up from the drag handle to perform the in app to home
  // gesture.
  GetEventGenerator()->GestureScrollSequence(
      start, start + gfx::Vector2d(0, -300), base::Milliseconds(10),
      /*num_steps = */ 5);

  // The nudge should be hidden when the gesture completes.
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests that drag handle nudge gets hidden when the user performs window drag
// from shelf to home.
TEST_F(DragHandleContextualNudgeTest, FlingFromShelfToHomeHidesTheNudge) {
  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  const gfx::Point start = drag_handle->GetBoundsInScreen().CenterPoint();
  // Simulates window drag from shelf gesture, and verifies that the timer to
  // hide the drag handle nudge is canceled when the window drag from shelf
  // starts.
  GetEventGenerator()->GestureScrollSequenceWithCallback(
      start, start + gfx::Vector2d(0, -300), base::Milliseconds(10),
      /*num_steps = */ 6,
      base::BindRepeating(
          [](DragHandle* drag_handle, ui::EventType type,
             const gfx::Vector2dF& offset) {
            const bool scroll_end = type == ui::EventType::kGestureScrollEnd;
            EXPECT_EQ(!scroll_end,
                      drag_handle->gesture_nudge_target_visibility());
          },
          drag_handle));

  // The nudge should be hidden when the gesture completes.
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests that drag handle nudge gets hidden when the user performs window drag
// from shelf, even if the gesture does not end up going home.
TEST_F(DragHandleContextualNudgeTest, DragFromShelfToHomeHidesTheNudge) {
  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  const gfx::Point start = drag_handle->GetBoundsInScreen().CenterPoint();
  // Simulates window drag from shelf gesture, and verifies that the timer to
  // hide the drag handle nudge is canceled when the window drag from shelf
  // starts.
  GetEventGenerator()->GestureScrollSequenceWithCallback(
      start, start + gfx::Vector2d(0, -150), base::Milliseconds(500),
      /*num_steps = */ 20,
      base::BindRepeating(
          [](DragHandle* drag_handle, ui::EventType type,
             const gfx::Vector2dF& offset) {
            DragWindowFromShelfController* window_drag_controller =
                GetShelfLayoutManager()->window_drag_controller_for_testing();
            if (window_drag_controller &&
                window_drag_controller->dragged_window()) {
              DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
                  window_drag_controller);
            }
            const bool scroll_end = type == ui::EventType::kGestureScrollEnd;
            EXPECT_EQ(!scroll_end,
                      drag_handle->gesture_nudge_target_visibility());
          },
          drag_handle));

  // The nudge should be hidden when the gesture completes.
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());

  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
}

// Tests that scheduled drag handle nudge show is canceled when overview starts.
TEST_F(DragHandleContextualNudgeTest, OverviewCancelsNudgeShow) {
  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  EnterOverview();
  ASSERT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());
}

// Tests that tapping the drag handle can shown drag handle nudge in overview.
TEST_F(DragHandleContextualNudgeTest, DragHandleTapShowNudgeInOverview) {
  TabletModeControllerTestApi().EnterTabletMode();

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
  drag_handle->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  TabletModeControllerTestApi().LeaveTabletMode();
  TabletModeControllerTestApi().EnterTabletMode();

  EnterOverview();
  ASSERT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());

  GetEventGenerator()->GestureTapAt(
      drag_handle->GetBoundsInScreen().CenterPoint());

  EXPECT_TRUE(drag_handle->GetVisible());
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  EXPECT_TRUE(drag_handle->has_hide_drag_handle_timer_for_testing());
  drag_handle->fire_hide_drag_handle_timer_for_testing();
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());

  // Tapping the drag handle again will show the nudge again.
  GetEventGenerator()->GestureTapAt(
      drag_handle->GetBoundsInScreen().CenterPoint());
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());
}

// Tests that tapping the drag handle in split screen does not show nudge.
TEST_F(DragHandleContextualNudgeTest,
       DragHandleTapDoesNotShowNudgeForSplitScreen) {
  TabletModeControllerTestApi().EnterTabletMode();
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  // Go into split view mode by first going into overview, and then snapping
  // the open window on one side.
  EnterOverview();
  SplitViewController* split_view_controller =
      SplitViewController::Get(shelf_widget->GetNativeWindow());
  split_view_controller->SnapWindow(window.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(split_view_controller->InSplitViewMode());

  // Tapping the drag handle will not show the drag handle.
  GetEventGenerator()->GestureTapAt(
      drag_handle->GetBoundsInScreen().CenterPoint());
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
}

// Tests that entering split screen hides the drag handle nudge.
TEST_F(DragHandleContextualNudgeTest, DragHandleNudgeHiddenOnSplitScreen) {
  TabletModeControllerTestApi().EnterTabletMode();

  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());

  ShelfWidget* const shelf_widget = GetShelfWidget();
  DragHandle* const drag_handle = shelf_widget->GetDragHandle();

  // Tapping the drag handle should show the drag handle.
  GetEventGenerator()->GestureTapAt(
      drag_handle->GetBoundsInScreen().CenterPoint());
  EXPECT_TRUE(drag_handle->gesture_nudge_target_visibility());

  // Go into split view mode by first going into overview, and then snapping
  // the open window on one side.
  EnterOverview();
  SplitViewController* split_view_controller =
      SplitViewController::Get(shelf_widget->GetNativeWindow());
  split_view_controller->SnapWindow(window.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(split_view_controller->InSplitViewMode());

  // The drag handle nudge should no longer be visible.
  EXPECT_FALSE(drag_handle->gesture_nudge_target_visibility());
}

TEST_P(DragHandleContextualNudgeTestA11yPrefs, HideNudgesForShelfControls) {
  SCOPED_TRACE(testing::Message() << "Pref=" << GetParam());
  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());

  // The drag handle should be showing but the nudge should not. A timer to show
  // the nudge should be initialized.
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  // Firing the timer should show the drag handle nudge.
  GetShelfWidget()->GetDragHandle()->fire_show_drag_handle_timer_for_testing();
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_TRUE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());

  // Enabling accessibility auto click disables gestures and enables shelf
  // control buttons. In app to home nudge should be hidden.
  Shell::Get()
      ->session_controller()
      ->GetLastActiveUserPrefService()
      ->SetBoolean(GetParam(), true);
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
}

TEST_P(DragHandleContextualNudgeTestA11yPrefs, DisableNudgesForShelfControls) {
  SCOPED_TRACE(testing::Message() << "Pref=" << GetParam());
  // Turn on accessibility settings to enable shelf controls.
  Shell::Get()
      ->session_controller()
      ->GetLastActiveUserPrefService()
      ->SetBoolean(GetParam(), true);

  // Creates a widget to put shelf into in-app state.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());
  // The drag handle should be showing but the nudge should not. A timer to show
  // the nudge should not be initialized because shelf controls are on.
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  EXPECT_FALSE(
      GetShelfWidget()->GetDragHandle()->gesture_nudge_target_visibility());
  EXPECT_FALSE(GetShelfWidget()
                   ->GetDragHandle()
                   ->has_show_drag_handle_timer_for_testing());
}

}  // namespace ash