chromium/ash/shelf/shelf_layout_manager_unittest.cc

// Copyright 2012 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/shelf/shelf_layout_manager.h"

#include <memory>
#include <optional>
#include <utility>

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accelerators/accelerator_table.h"
#include "ash/accessibility/accessibility_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/app_list/views/app_list_view.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/drag_drop/drag_drop_controller.h"
#include "ash/focus_cycler.h"
#include "ash/keyboard/keyboard_controller_impl.h"
#include "ash/keyboard/ui/keyboard_ui.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/keyboard_util.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/ash_prefs.h"
#include "ash/public/cpp/keyboard/keyboard_controller.h"
#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/shelf_prefs.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller_observer.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.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/home_button.h"
#include "ash/shelf/hotseat_widget.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_app_button.h"
#include "ash/shelf/shelf_controller.h"
#include "ash/shelf/shelf_focus_cycler.h"
#include "ash/shelf/shelf_layout_manager_observer.h"
#include "ash/shelf/shelf_metrics.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_test_util.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shelf/test/hotseat_state_watcher.h"
#include "ash/shelf/test/shelf_layout_manager_test_base.h"
#include "ash/shell.h"
#include "ash/system/eche/eche_tray.h"
#include "ash/system/ime_menu/ime_menu_tray.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/test_ash_web_view_factory.h"
#include "ash/test/test_widget_builder.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wm/lock_state_controller.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_types.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/work_area_insets.h"
#include "ash/wm/workspace_controller.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/icu_test_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/ui_base_switches.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/display.h"
#include "ui/display/display_layout.h"
#include "ui/display/display_observer.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/base_event_utils.h"
#include "ui/events/event_constants.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/views/animation/bounds_animator.h"
#include "ui/views/test/widget_animation_waiter.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

using ::chromeos::kHideShelfWhenFullscreenKey;

void PressHomeButton() {
  Shell::Get()->app_list_controller()->ToggleAppList(
      display::Screen::GetScreen()->GetPrimaryDisplay().id(),
      AppListShowSource::kShelfButton, base::TimeTicks());
}

void StepWidgetLayerAnimatorToEnd(views::Widget* widget) {
  widget->GetNativeView()->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
                                                        base::Seconds(1));
}

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

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

HotseatWidget* GetHotseatWidget() {
  return AshTestBase::GetPrimaryShelf()->hotseat_widget();
}

gfx::Rect GetScreenAvailableBounds() {
  const WorkAreaInsets* const work_area =
      WorkAreaInsets::ForWindow(GetShelfWidget()->GetNativeWindow());
  gfx::Rect available_bounds = screen_util::GetDisplayBoundsWithShelf(
      GetShelfWidget()->GetNativeWindow());
  available_bounds.Inset(work_area->GetAccessibilityInsets());
  return available_bounds;
}

// Returns the distance of the top of a widget from the bottom of the primary
// screen.
int GetWidgetOffsetFromBottom(const views::Widget* widget) {
  const int display_bottom =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds().bottom();

  return display_bottom -
         widget->GetClientAreaBoundsInScreen().top_center().y();
}

// Creates and displays a test app in the hotseat.
void AddApp() {
  ShelfController* controller = Shell::Get()->shelf_controller();
  const int next_app_index = controller->model()->item_count();
  ShelfTestUtil::AddAppShortcut(
      "app_id_" + base::NumberToString(next_app_index), TYPE_PINNED_APP);
}

class TestDisplayObserver : public display::DisplayObserver {
 public:
  TestDisplayObserver() = default;

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

  ~TestDisplayObserver() override = default;

  int metrics_change_count() const { return metrics_change_count_; }

 private:
  // ShelfLayoutManagerObserver:
  void OnDisplayMetricsChanged(const display::Display& display,
                               uint32_t changed_metrics) override {
    metrics_change_count_++;
  }

  display::ScopedDisplayObserver display_observer_{this};
  int metrics_change_count_ = 0;
};

class WallpaperShownWaiter : public WallpaperControllerObserver {
 public:
  WallpaperShownWaiter() {
    Shell::Get()->wallpaper_controller()->AddObserver(this);
  }

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

  ~WallpaperShownWaiter() override {
    Shell::Get()->wallpaper_controller()->RemoveObserver(this);
  }

  // Note this could only be called once because RunLoop would not run after
  // Quit is called. Create a new instance if there's need to wait again.
  void Wait() { run_loop_.Run(); }

 private:
  // WallpaperControllerObserver:
  void OnFirstWallpaperShown() override { run_loop_.Quit(); }

  base::RunLoop run_loop_;
};

// This class detects the auto hide state change in shelf layout manager within
// its lifetime.
class AutoHideStateDetector : public ShelfLayoutManagerObserver {
 public:
  AutoHideStateDetector() {
    Shell::GetPrimaryRootWindowController()
        ->shelf()
        ->shelf_layout_manager()
        ->AddObserver(this);
  }

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

  ~AutoHideStateDetector() override {
    Shell::GetPrimaryRootWindowController()
        ->shelf()
        ->shelf_layout_manager()
        ->RemoveObserver(this);
  }

  void OnAutoHideStateChanged(ShelfAutoHideState new_state) override {
    if (new_state == SHELF_AUTO_HIDE_HIDDEN)
      was_shelf_auto_hidden = true;
  }

  bool WasShelfAutoHidden() const { return was_shelf_auto_hidden; }

 private:
  bool was_shelf_auto_hidden = false;
};

}  // namespace

class ShelfLayoutManagerTest : public ShelfLayoutManagerTestBase {
 public:
  ShelfLayoutManagerTest() {
    // TODO(b/293400777): Test currently crashes when Jelly is enabled because
    // of a crash in ShellTestApi. Remove when that is fixed.
    scoped_features_.InitAndDisableFeature(chromeos::features::kJelly);
  }

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

 private:
  base::test::ScopedFeatureList scoped_features_;
};

// Makes sure SetVisible updates work area and widget appropriately.
TEST_F(ShelfLayoutManagerTest, SetVisible) {
  ShelfWidget* shelf_widget = GetShelfWidget();
  ShelfLayoutManager* manager = shelf_widget->shelf_layout_manager();
  // Force an initial layout.
  manager->LayoutShelf();
  EXPECT_EQ(SHELF_VISIBLE, manager->visibility_state());

  gfx::Rect status_bounds(
      shelf_widget->status_area_widget()->GetWindowBoundsInScreen());
  gfx::Rect shelf_bounds(shelf_widget->GetWindowBoundsInScreen());
  int shelf_height = manager->GetIdealBounds().height();
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  const gfx::Rect stable_work_area = display.work_area();
  ASSERT_NE(-1, display.id());
  // Bottom inset should be the max of widget heights.
  EXPECT_EQ(shelf_height, display.GetWorkAreaInsets().bottom());

  // Hide the shelf.
  SetState(manager, SHELF_HIDDEN);
  // Run the animation to completion.
  StepWidgetLayerAnimatorToEnd(shelf_widget);
  StepWidgetLayerAnimatorToEnd(shelf_widget->status_area_widget());
  EXPECT_EQ(SHELF_HIDDEN, manager->visibility_state());
  display = display::Screen::GetScreen()->GetPrimaryDisplay();
  EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());

  // Make sure the bounds of the two widgets changed.
  EXPECT_GE(shelf_widget->GetNativeView()->bounds().y(),
            display.bounds().bottom());
  EXPECT_GE(shelf_widget->status_area_widget()->GetNativeView()->bounds().y(),
            display.bounds().bottom());
  EXPECT_EQ(stable_work_area,
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());

  // And show it again.
  SetState(manager, SHELF_VISIBLE);
  // Run the animation to completion.
  StepWidgetLayerAnimatorToEnd(shelf_widget);
  StepWidgetLayerAnimatorToEnd(shelf_widget->status_area_widget());
  EXPECT_EQ(SHELF_VISIBLE, manager->visibility_state());
  display = display::Screen::GetScreen()->GetPrimaryDisplay();
  EXPECT_EQ(shelf_height, display.GetWorkAreaInsets().bottom());
  EXPECT_EQ(stable_work_area,
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());

  // Make sure the bounds of the two widgets changed.
  shelf_bounds = shelf_widget->GetNativeView()->bounds();
  EXPECT_LT(shelf_bounds.y(), display.bounds().bottom());
  status_bounds = shelf_widget->status_area_widget()->GetNativeView()->bounds();
  EXPECT_LT(status_bounds.y(), display.bounds().bottom());
}

// Makes sure LayoutShelf invoked while animating cleans things up.
TEST_F(ShelfLayoutManagerTest, LayoutShelfWhileAnimating) {
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  // Force an initial layout.
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Hide the shelf.
  SetState(layout_manager, SHELF_HIDDEN);
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());

  // Make sure the bounds of the two widgets changed.
  ShelfWidget* shelf_widget = GetShelfWidget();
  EXPECT_GE(shelf_widget->GetNativeView()->bounds().y(),
            display.bounds().bottom());
  EXPECT_GE(shelf_widget->status_area_widget()->GetNativeView()->bounds().y(),
            display.bounds().bottom());
}

// Test that switching to a different visibility state does not restart the
// shelf show / hide animation if it is already running. (crbug.com/250918)
TEST_F(ShelfLayoutManagerTest, SetStateWhileAnimating) {
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  SetState(layout_manager, SHELF_VISIBLE);
  ShelfWidget* shelf_widget = GetShelfWidget();
  gfx::Rect initial_shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
  gfx::Rect initial_status_bounds =
      shelf_widget->status_area_widget()->GetWindowBoundsInScreen();

  ui::ScopedAnimationDurationScaleMode normal_animation_duration(
      ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);
  SetState(layout_manager, SHELF_HIDDEN);
  SetState(layout_manager, SHELF_VISIBLE);

  gfx::Rect current_shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
  gfx::Rect current_status_bounds =
      shelf_widget->status_area_widget()->GetWindowBoundsInScreen();

  const int small_change = initial_shelf_bounds.height() / 2;
  EXPECT_LE(
      std::abs(initial_shelf_bounds.height() - current_shelf_bounds.height()),
      small_change);
  EXPECT_LE(
      std::abs(initial_status_bounds.height() - current_status_bounds.height()),
      small_change);
}

// Various assertions around auto-hide.
TEST_F(ShelfLayoutManagerTest, AutoHide) {
  ui::test::EventGenerator* generator = GetEventGenerator();

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

  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();

  // LayoutShelf() forces the animation to completion, at which point the
  // shelf should go off the screen.
  layout_manager->LayoutShelf();

  const int display_bottom = display.bounds().bottom();
  EXPECT_EQ(
      display_bottom - ShelfConfig::Get()->hidden_shelf_in_screen_portion(),
      GetShelfWidget()->GetWindowBoundsInScreen().y());
  EXPECT_EQ(display_bottom, display.work_area().bottom());
  EXPECT_EQ(stable_work_area,
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());

  // Move the mouse to the bottom of the screen.
  generator->MoveMouseTo(0, display_bottom - 1);

  // Shelf should be shown again (but it shouldn't have changed the work area).
  SetState(layout_manager, SHELF_AUTO_HIDE);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  layout_manager->LayoutShelf();
  EXPECT_EQ(display_bottom - layout_manager->GetIdealBounds().height(),
            GetShelfWidget()->GetWindowBoundsInScreen().y());
  EXPECT_EQ(display_bottom, display.work_area().bottom());
  EXPECT_EQ(stable_work_area,
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());

  // Tap the system tray when shelf is shown should open the system tray menu.
  generator->GestureTapAt(
      GetPrimaryUnifiedSystemTray()->GetBoundsInScreen().CenterPoint());
  EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());

  // Move mouse back up and click to dismiss the opened system tray menu.
  generator->MoveMouseTo(0, 0);
  generator->ClickLeftButton();
  SetState(layout_manager, SHELF_AUTO_HIDE);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  layout_manager->LayoutShelf();
  EXPECT_EQ(
      display_bottom - ShelfConfig::Get()->hidden_shelf_in_screen_portion(),
      GetShelfWidget()->GetWindowBoundsInScreen().y());
  EXPECT_EQ(stable_work_area,
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());

  // Move the mouse to the bottom again to show the shelf.
  generator->MoveMouseTo(0, display_bottom - 1);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // A tap on the maximized window should hide the shelf, even if the most
  // recent mouse position was over the shelf (crbug.com/963977).
  EXPECT_TRUE(widget->IsMouseEventsEnabled());
  gfx::Rect window_bounds = widget->GetNativeWindow()->GetBoundsInScreen();
  generator->GestureTapAt(window_bounds.origin() + gfx::Vector2d(10, 10));
  EXPECT_FALSE(widget->IsMouseEventsEnabled());
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Return the mouse to the top.
  generator->MoveMouseTo(0, 0);

  // Drag mouse to bottom of screen.
  generator->PressLeftButton();
  generator->MoveMouseTo(0, display_bottom - 1);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  generator->ReleaseLeftButton();
  generator->MoveMouseTo(1, display_bottom - 1);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  generator->PressLeftButton();
  generator->MoveMouseTo(1, display_bottom - 1);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Switch to tablet mode should hide the AUTO_HIDE_SHOWN shelf even the mouse
  // cursor is inside the shelf area.
  EXPECT_FALSE(display::Screen::GetScreen()->InTabletMode());
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Test the behavior of the shelf when it is auto hidden and it is on the
// boundary between the primary and the secondary display.
TEST_F(ShelfLayoutManagerTest, AutoHideShelfOnScreenBoundary) {
  UpdateDisplay("800x600,800x600");
  Shell::Get()->display_manager()->SetLayoutForCurrentDisplays(
      display::test::CreateDisplayLayout(display_manager(),
                                         display::DisplayPlacement::RIGHT, 0));
  // Put the primary monitor's shelf on the display boundary.
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAlignment(ShelfAlignment::kRight);

  // Create a window because the shelf is always shown when no windows are
  // visible.
  CreateTestWidget();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());

  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  const int right_edge = display.bounds().right() - 1;
  const int y = display.bounds().y();

  // Start off the mouse nowhere near the shelf; the shelf should be hidden.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(right_edge - 50, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Moving the mouse over the light bar (but not to the edge of the screen)
  // should show the shelf.
  generator->MoveMouseTo(right_edge - 1, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(right_edge - 1,
            display::Screen::GetScreen()->GetCursorScreenPoint().x());

  // Moving the mouse off the light bar should hide the shelf.
  generator->MoveMouseTo(right_edge - 60, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Moving the mouse to the right edge of the screen crossing the light bar
  // should show the shelf despite the mouse cursor getting warped to the
  // secondary display.
  generator->MoveMouseTo(right_edge - 1, y);
  generator->MoveMouseTo(right_edge, y);
  UpdateAutoHideStateNow();
  EXPECT_NE(right_edge - 1,
            display::Screen::GetScreen()->GetCursorScreenPoint().x());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Hide the shelf.
  generator->MoveMouseTo(right_edge - 60, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Moving the mouse to the right edge of the screen crossing the light bar and
  // overshooting by a lot should keep the shelf hidden.
  generator->MoveMouseTo(right_edge - 1, y);
  generator->MoveMouseTo(right_edge + 50, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Moving the mouse to the right edge of the screen crossing the light bar and
  // overshooting a bit should show the shelf.
  generator->MoveMouseTo(right_edge - 1, y);
  generator->MoveMouseTo(right_edge + 2, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Keeping the mouse close to the left edge of the secondary display after the
  // shelf is shown should keep the shelf shown.
  generator->MoveMouseTo(right_edge + 2, y + 1);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Moving the mouse far from the left edge of the secondary display should
  // hide the shelf.
  generator->MoveMouseTo(right_edge + 50, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Moving to the left edge of the secondary display without first crossing
  // the primary display's right aligned shelf first should not show the shelf.
  generator->MoveMouseTo(right_edge + 2, y);
  UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Assertions around the login screen.
TEST_F(ShelfLayoutManagerTest, VisibleWhenLoginScreenShowing) {
  Shelf* shelf = GetPrimaryShelf();
  auto* wallpaper_controller = Shell::Get()->wallpaper_controller();
  WallpaperShownWaiter waiter;

  SessionInfo info;
  info.state = session_manager::SessionState::LOGIN_PRIMARY;
  Shell::Get()->session_controller()->SetSessionInfo(info);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // No wallpaper.
  ASSERT_FALSE(wallpaper_controller->HasShownAnyWallpaper());
  EXPECT_EQ(ShelfBackgroundType::kLogin,
            GetShelfLayoutManager()->shelf_background_type());

  // Showing wallpaper is asynchronous.
  wallpaper_controller->ShowDefaultWallpaperForTesting();
  waiter.Wait();
  ASSERT_TRUE(wallpaper_controller->HasShownAnyWallpaper());

  // Non-blurred wallpaper.
  wallpaper_controller->UpdateWallpaperBlurForLockState(/*blur=*/false);
  EXPECT_EQ(ShelfBackgroundType::kLoginNonBlurredWallpaper,
            GetShelfLayoutManager()->shelf_background_type());

  // Blurred wallpaper.
  wallpaper_controller->UpdateWallpaperBlurForLockState(/*blur=*/true);
  EXPECT_EQ(ShelfBackgroundType::kLogin,
            GetShelfLayoutManager()->shelf_background_type());
}

// Assertions around the lock screen showing.
TEST_F(ShelfLayoutManagerTest, VisibleWhenLockScreenShowing) {
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // LayoutShelf() forces the animation to completion, at which point the
  // shelf should go off the screen.
  layout_manager->LayoutShelf();
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  EXPECT_EQ(display.bounds().bottom() -
                ShelfConfig::Get()->hidden_shelf_in_screen_portion(),
            GetShelfWidget()->GetWindowBoundsInScreen().y());

  std::unique_ptr<views::Widget> lock_widget(AshTestBase::CreateTestWidget(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, nullptr,
      kShellWindowId_LockScreenContainer, gfx::Rect(200, 200)));
  lock_widget->Maximize();

  // Lock the screen.
  LockScreen();
  // Showing a widget in the lock screen should force the shelf to be visible.
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfBackgroundType::kLogin,
            GetShelfLayoutManager()->shelf_background_type());

  UnlockScreen();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());
}

// Verifies that the hidden shelf shows after triggering the
// AcceleratorAction::kFocusShelf accelerator (https://crbug.com/1111426).
TEST_F(ShelfLayoutManagerTest, ShowHiddenShelfByFocusShelfAccelerator) {
  // Open a window so that the shelf will auto-hide.
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  window->Show();
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Focus on the shelf by accelerator.
  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
      AcceleratorAction::kFocusShelf, {});

  // Shelf should be visible.
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
}

TEST_F(ShelfLayoutManagerTest, ShelfDoesNotAutoHideWithVoxAndTabletMode) {
  TabletModeControllerTestApi().EnterTabletMode();
  // Open a window so that the shelf will auto-hide.
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  window->Show();
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Enable Chromevox. The shelf should now show.
  Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
      true, A11Y_NOTIFICATION_NONE);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Disable Chromevox again. The shelf should be able to auto-hide again.
  Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
      false, A11Y_NOTIFICATION_NONE);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Tests that the shelf should be visible when in overview mode.
TEST_F(ShelfLayoutManagerTest, VisibleInOverview) {
  ui::ScopedAnimationDurationScaleMode regular_animations(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  std::unique_ptr<aura::Window> window(CreateTestWindow());
  window->SetBounds({0, 0, 120, 320});
  window->Show();
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // LayoutShelf() forces the animation to completion, at which point the
  // shelf should go off the screen.
  GetShelfLayoutManager()->LayoutShelf();
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  EXPECT_EQ(display.bounds().bottom() -
                ShelfConfig::Get()->hidden_shelf_in_screen_portion(),
            GetShelfWidget()->GetWindowBoundsInScreen().y());

  // Tests that the shelf is visible when in overview mode.
  EnterOverview();
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kEnterAnimationComplete);

  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfBackgroundType::kOverview,
            GetShelfLayoutManager()->shelf_background_type());

  // Test that on exiting overview mode, the shelf returns to auto hide state.
  ExitOverview();
  ShellTestApi().WaitForOverviewAnimationState(
      OverviewAnimationState::kExitAnimationComplete);

  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(display.bounds().bottom() -
                ShelfConfig::Get()->hidden_shelf_in_screen_portion(),
            GetShelfWidget()->GetNativeWindow()->GetTargetBounds().y());
}

TEST_F(ShelfLayoutManagerTest,
       HotseatStateAfterTabletModeTransitionWithOverview) {
  // Tests that the shelf is visible when in overview mode.
  EnterOverview();
  ASSERT_EQ(HotseatState::kShownClamshell,
            GetShelfLayoutManager()->hotseat_state());

  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());
}

// Assertions around SetAutoHideBehavior.
TEST_F(ShelfLayoutManagerTest, SetAutoHideBehavior) {
  Shelf* shelf = GetPrimaryShelf();
  views::Widget* widget = CreateTestWidget();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  widget->Maximize();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  display::Screen* screen = display::Screen::GetScreen();
  EXPECT_EQ(screen->GetPrimaryDisplay().work_area().bottom(),
            widget->GetWorkAreaBoundsInScreen().bottom());

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(screen->GetPrimaryDisplay().work_area().bottom(),
            widget->GetWorkAreaBoundsInScreen().bottom());

  ui::ScopedAnimationDurationScaleMode animation_duration(
      ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  ShelfWidget* shelf_widget = GetShelfWidget();
  EXPECT_TRUE(shelf_widget->status_area_widget()->IsVisible());
  StepWidgetLayerAnimatorToEnd(shelf_widget);
  StepWidgetLayerAnimatorToEnd(shelf_widget->status_area_widget());
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  EXPECT_EQ(screen->GetPrimaryDisplay().work_area().bottom(),
            widget->GetWorkAreaBoundsInScreen().bottom());
}

// Verifies the shelf is visible when status/shelf is focused.
TEST_F(ShelfLayoutManagerTest, VisibleWhenStatusOrShelfFocused) {
  Shelf* shelf = GetPrimaryShelf();
  views::Widget* widget = CreateTestWidget();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Focus the shelf. Have to go through the focus cycler as normal focus
  // requests to it do nothing.
  GetShelfWidget()->GetFocusCycler()->RotateFocus(FocusCycler::FORWARD);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  widget->Activate();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Trying to activate the status should fail, since we only allow activating
  // it when the user is using the keyboard (i.e. through FocusCycler).
  GetShelfWidget()->status_area_widget()->Activate();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  GetShelfWidget()->GetFocusCycler()->RotateFocus(FocusCycler::FORWARD);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
}

// Checks that the status area follows along the auto-hidden shelf when the
// user swipes it up or down.
TEST_F(ShelfLayoutManagerTest, StatusAreaMoveWithSwipeOnAutoHiddenShelf) {
  Shelf* shelf = GetPrimaryShelf();
  CreateTestWidget();
  TabletModeControllerTestApi().EnterTabletMode();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  const int hidden_shelf_in_screen_portion =
      ShelfConfig::Get()->hidden_shelf_in_screen_portion();

  // The shelf is hidden. The status area should also be off-screen.
  EXPECT_EQ(hidden_shelf_in_screen_portion,
            GetWidgetOffsetFromBottom(shelf->status_area_widget()));

  gfx::Rect display_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  const gfx::Point start(display_bounds.bottom_center());
  const gfx::Point middle(start + gfx::Vector2d(0, -40));
  const gfx::Point end(start + gfx::Vector2d(0, -80));

  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveTouch(start);
  generator->PressTouch();

  // The drag has just started, but we haven't moved yet.
  EXPECT_EQ(hidden_shelf_in_screen_portion,
            GetWidgetOffsetFromBottom(shelf->status_area_widget()));

  generator->MoveTouch(middle);

  // Now the status area should have entered the screen.
  const int status_area_visible_px_mid_gesture =
      GetWidgetOffsetFromBottom(shelf->status_area_widget());
  EXPECT_LT(hidden_shelf_in_screen_portion, status_area_visible_px_mid_gesture);

  // Finish the gesture, the status area should follow.
  generator->MoveTouch(end);
  generator->ReleaseTouch();

  const int status_area_visible_px_end_gesture =
      GetWidgetOffsetFromBottom(shelf->status_area_widget());
  EXPECT_LT(status_area_visible_px_mid_gesture,
            status_area_visible_px_end_gesture);

  // Now start swiping down. The status area should follow the other way.
  generator->MoveTouch(end);
  generator->PressTouch();
  EXPECT_EQ(status_area_visible_px_end_gesture,
            GetWidgetOffsetFromBottom(shelf->status_area_widget()));

  // And it should be back to off-screen after the gesture ends.
  generator->MoveTouch(start);
  generator->ReleaseTouch();

  EXPECT_EQ(hidden_shelf_in_screen_portion,
            GetWidgetOffsetFromBottom(shelf->status_area_widget()));
}

// Checks that the shelf keeps hidden during the Kiosk mode.
TEST_F(ShelfLayoutManagerTest, HiddenShelfInKioskMode_FullScreen) {
  SetUpKioskSession();

  // Create a window and make it full screen; the shelf should be hidden.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  window->SetProperty(kHideShelfWhenFullscreenKey, false);
  window->Show();
  wm::ActivateWindow(window);
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_HIDDEN, GetPrimaryShelf()->GetVisibilityState());
  EXPECT_EQ(WorkspaceWindowState::kFullscreen, GetWorkspaceWindowState());

  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_HIDDEN, GetPrimaryShelf()->GetVisibilityState());
}

// Checks that the shelf keeps hidden during the Kiosk mode. (Some windows might
// not be fullscreen, e.g., the a11y setting window.)
TEST_F(ShelfLayoutManagerTest, HiddenShelfInKioskMode_Default) {
  SetUpKioskSession();

  // Create a default window; the shelf should be hidden.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->SetProperty(kHideShelfWhenFullscreenKey, false);
  window->Show();
  wm::ActivateWindow(window);
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_HIDDEN, GetPrimaryShelf()->GetVisibilityState());
  EXPECT_EQ(WorkspaceWindowState::kDefault, GetWorkspaceWindowState());

  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_HIDDEN, GetPrimaryShelf()->GetVisibilityState());
}

TEST_F(ShelfLayoutManagerTest,
       NavigationWidgetDoesNotMoveWithoutAutoHiddenShelf) {
  Shelf* shelf = GetPrimaryShelf();
  CreateTestWidget();
  TabletModeControllerTestApi().EnterTabletMode();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  gfx::Rect nav_widget_bounds =
      shelf->navigation_widget()->GetWindowBoundsInScreen();

  const gfx::Point end(nav_widget_bounds.top_center());
  const gfx::Point middle(end +
                          gfx::Vector2d(0, -nav_widget_bounds.height() / 2));
  const gfx::Point start(end + gfx::Vector2d(0, -nav_widget_bounds.height()));

  // Perform a drag down on the status area widget.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveTouch(start);
  generator->PressTouch();
  generator->MoveTouch(middle);
  EXPECT_EQ(nav_widget_bounds,
            shelf->navigation_widget()->GetWindowBoundsInScreen());
  generator->MoveTouch(end);
  EXPECT_EQ(nav_widget_bounds,
            shelf->navigation_widget()->GetWindowBoundsInScreen());
  generator->ReleaseTouch();
  EXPECT_EQ(nav_widget_bounds,
            shelf->navigation_widget()->GetWindowBoundsInScreen());
}

TEST_F(ShelfLayoutManagerTest, StatusWidgetDoesNotMoveWithoutAutoHiddenShelf) {
  Shelf* shelf = GetPrimaryShelf();
  CreateTestWidget();
  TabletModeControllerTestApi().EnterTabletMode();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  gfx::Rect status_widget_bounds =
      shelf->status_area_widget()->GetWindowBoundsInScreen();

  const gfx::Point end(status_widget_bounds.top_center());
  const gfx::Point middle(end +
                          gfx::Vector2d(0, -status_widget_bounds.height() / 2));
  const gfx::Point start(end +
                         gfx::Vector2d(0, -status_widget_bounds.height()));

  // Perform a drag down on the status area widget.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveTouch(start);
  generator->PressTouch();
  generator->MoveTouch(middle);
  EXPECT_EQ(status_widget_bounds,
            shelf->status_area_widget()->GetWindowBoundsInScreen());
  generator->MoveTouch(end);
  EXPECT_EQ(status_widget_bounds,
            shelf->status_area_widget()->GetWindowBoundsInScreen());
  generator->ReleaseTouch();
  EXPECT_EQ(status_widget_bounds,
            shelf->status_area_widget()->GetWindowBoundsInScreen());
}

// Checks that the navigation widget follows along the auto-hidden shelf when
// the user swipes it up or down.
TEST_F(ShelfLayoutManagerTest, NavigationWidgetMoveWithSwipeOnAutoHiddenShelf) {
  Shell::Get()
      ->accessibility_controller()
      ->SetTabletModeShelfNavigationButtonsEnabled(true);

  Shelf* shelf = GetPrimaryShelf();
  CreateTestWidget();
  TabletModeControllerTestApi().EnterTabletMode();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  const int hidden_shelf_in_screen_portion =
      ShelfConfig::Get()->hidden_shelf_in_screen_portion();

  // The shelf is hidden. The navigation widget should also be off-screen.
  EXPECT_EQ(hidden_shelf_in_screen_portion,
            GetWidgetOffsetFromBottom(shelf->navigation_widget()));

  gfx::Rect display_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  const gfx::Point start(display_bounds.bottom_center());
  const gfx::Point middle(start + gfx::Vector2d(0, -40));
  const gfx::Point end(start + gfx::Vector2d(0, -80));

  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveTouch(start);
  generator->PressTouch();

  // The drag has just started, but we haven't moved yet.
  EXPECT_EQ(hidden_shelf_in_screen_portion,
            GetWidgetOffsetFromBottom(shelf->navigation_widget()));

  generator->MoveTouch(middle);

  // Now the navigation widget should have entered the screen.
  const int navigation_visible_px_mid_gesture =
      GetWidgetOffsetFromBottom(shelf->navigation_widget());
  EXPECT_LT(hidden_shelf_in_screen_portion, navigation_visible_px_mid_gesture);

  // Verify that the navigation widget and status area moved the same amount.
  EXPECT_EQ(navigation_visible_px_mid_gesture,
            GetWidgetOffsetFromBottom(shelf->status_area_widget()));

  // Finish the gesture, the navigation widget should follow.
  generator->MoveTouch(end);
  generator->ReleaseTouch();

  const int navigation_visible_px_end_gesture =
      GetWidgetOffsetFromBottom(shelf->navigation_widget());
  EXPECT_LT(navigation_visible_px_mid_gesture,
            navigation_visible_px_end_gesture);

  // Now start swiping down. The navigation widget should follow the other way.
  generator->MoveTouch(end);
  generator->PressTouch();
  EXPECT_EQ(navigation_visible_px_end_gesture,
            GetWidgetOffsetFromBottom(shelf->navigation_widget()));

  // And it should be back to off-screen after the gesture ends.
  generator->MoveTouch(start);
  generator->ReleaseTouch();

  EXPECT_EQ(hidden_shelf_in_screen_portion,
            GetWidgetOffsetFromBottom(shelf->navigation_widget()));
}

// Ensure a SHELF_VISIBLE shelf stays visible when the app list is shown.
TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfVisibleState) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);

  // Create a normal unmaximized window; the shelf should be visible.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->Show();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Show the app list and the shelf stays visible.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Hide the app list and the shelf stays visible.
  GetAppListTestHelper()->DismissAndRunLoop();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
}

// Ensure a SHELF_AUTO_HIDE shelf is shown temporarily (SHELF_AUTO_HIDE_SHOWN)
// when the app list is shown, but the visibility state doesn't change.
TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfAutoHideState) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  // Create a normal unmaximized window; the shelf should be hidden.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->Show();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Show the app list and the shelf should be temporarily visible.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  // The shelf's auto hide state won't be changed until the timer fires, so
  // force it to update now.
  GetShelfLayoutManager()->UpdateVisibilityState(/*force_layout=*/false);
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Hide the app list and the shelf should be hidden again.
  GetAppListTestHelper()->DismissAndRunLoop();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Makes sure that when we have dual displays, with one or both shelves are set
// to AutoHide, viewing the AppList on one of them doesn't unhide the other
// hidden shelf.
TEST_F(ShelfLayoutManagerTest, DualDisplayOpenAppListWithShelfAutoHideState) {
  // Create two displays.
  UpdateDisplay("0+0-200x300,+200+0-100x200");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  EXPECT_EQ(root_windows.size(), 2U);

  // Get the shelves in both displays and set them to be 'AutoHide'.
  Shelf* shelf_1 = Shelf::ForWindow(root_windows[0]);
  Shelf* shelf_2 = Shelf::ForWindow(root_windows[1]);
  EXPECT_NE(shelf_1, shelf_2);
  EXPECT_NE(shelf_1->GetWindow()->GetRootWindow(),
            shelf_2->GetWindow()->GetRootWindow());
  shelf_1->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  shelf_1->shelf_layout_manager()->LayoutShelf();
  shelf_2->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  shelf_2->shelf_layout_manager()->LayoutShelf();

  // Create a window in each display and show them in maximized state.
  aura::Window* window_1 = CreateTestWindowInParent(root_windows[0]);
  window_1->SetBounds(gfx::Rect(0, 0, 100, 100));
  window_1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  window_1->Show();
  aura::Window* window_2 = CreateTestWindowInParent(root_windows[1]);
  window_2->SetBounds(gfx::Rect(201, 0, 100, 100));
  window_2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  window_2->Show();

  EXPECT_EQ(shelf_1->GetWindow()->GetRootWindow(), window_1->GetRootWindow());
  EXPECT_EQ(shelf_2->GetWindow()->GetRootWindow(), window_2->GetRootWindow());

  // Activate one window in one display.
  wm::ActivateWindow(window_1);

  Shelf::UpdateShelfVisibility();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_1->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_2->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_1->GetAutoHideState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_2->GetAutoHideState());

  // Show the app list; only the shelf on the same display should be shown.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  Shelf::UpdateShelfVisibility();
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_1->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf_1->GetAutoHideState());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_2->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_2->GetAutoHideState());

  // Hide the app list, both shelves should be hidden.
  GetAppListTestHelper()->DismissAndRunLoop();
  Shelf::UpdateShelfVisibility();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_1->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_2->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_1->GetAutoHideState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_2->GetAutoHideState());
}

// Ensure a SHELF_HIDDEN shelf (for a fullscreen window) is shown temporarily
// when the app list is shown, and hidden again when the app list is dismissed.
TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfHiddenState) {
  Shelf* shelf = GetPrimaryShelf();

  // Create a window and make it full screen; the shelf should be hidden.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  window->Show();
  wm::ActivateWindow(window);
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
  EXPECT_EQ(WorkspaceWindowState::kFullscreen, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  // Show the app list and the shelf should be temporarily visible.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Hide the app list and the shelf should be hidden again.
  GetAppListTestHelper()->DismissAndRunLoop();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
}

// Test that in tablet mode with auto hide enabled, opening a tray bubble while
// closing another keeps the hotseat hidden.
TEST_F(ShelfLayoutManagerTest, HotseatExtendingWhileClosingTrayBubble) {
  TabletModeControllerTestApi().EnterTabletMode();
  ui::test::EventGenerator* generator = GetEventGenerator();
  Shelf* shelf = GetPrimaryShelf();
  StatusAreaWidget* status_area = shelf->status_area_widget();
  status_area->ime_menu_tray()->SetVisiblePreferred(true);

  // Set the shelf to be auto hidden.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Create a widget. The shelf and hotseat should become hidden.
  CreateTestWidget()->GetNativeWindow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());

  // Swipe up to show the auto-hide shelf, and extend the hotseat.
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());

  // Tap on the ime menu tray button, to show a tray bubble. The shelf
  // should be showing, but the hotseat should be hidden.
  EXPECT_FALSE(status_area->ime_menu_tray()->GetBubbleView());
  generator->GestureTapAt(
      status_area->ime_menu_tray()->GetBoundsInScreen().CenterPoint());
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(status_area->ime_menu_tray()->GetBubbleView());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());

  // Tap on the unified system tray to open its bubble and close the ime menu
  // bubble. The hotseat should remain in the hidden state.
  EXPECT_FALSE(status_area->IsMessageBubbleShown());
  generator->GestureTapAt(
      status_area->unified_system_tray()->GetBoundsInScreen().CenterPoint());
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(status_area->IsMessageBubbleShown());
  EXPECT_FALSE(status_area->ime_menu_tray()->GetBubbleView());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());

  // Check that the status bubble and shelf are hidden after tapping on the
  // in-app shelf.
  generator->GestureTapAt(
      GetShelfWidget()->GetVisibleShelfBounds().CenterPoint());
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(status_area->IsMessageBubbleShown());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());
}

// With a fullscreen window, ensure the hidden shelf is shown temporarily when
// the app list is shown and when tray bubbles are shown. Ensure that the shelf
// is hidden again once tray bubbles are closed.
TEST_F(ShelfLayoutManagerTest, OpenAppListInFullscreenWithShelfHiddenState) {
  Shelf* shelf = GetPrimaryShelf();
  StatusAreaWidget* status_area = shelf->status_area_widget();
  status_area->ime_menu_tray()->SetVisiblePreferred(true);

  // Create a window and make it full screen; the shelf should be hidden.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  window->Show();
  wm::ActivateWindow(window);
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
  EXPECT_EQ(WorkspaceWindowState::kFullscreen, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  // Show the app list and the shelf should be temporarily visible.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Click on the ime menu tray button, to show a tray bubble. The shelf
  // should still be showing and the app list should hide.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(
      status_area->ime_menu_tray()->GetBoundsInScreen().CenterPoint());
  generator->PressLeftButton();
  base::RunLoop().RunUntilIdle();
  generator->ReleaseLeftButton();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);

  // Click away from the shelf and tray bubble to hide the shelf.
  generator->MoveMouseTo(10, 10);
  generator->ClickLeftButton();
  EXPECT_TRUE(RunVisibilityUpdateForTrayCallback());
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());

  // Show the app list and the shelf should be temporarily visible.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Click on the unified system tray button, opening the tray and hiding the
  // app list.
  EXPECT_FALSE(status_area->IsMessageBubbleShown());
  generator->MoveMouseTo(
      status_area->unified_system_tray()->GetBoundsInScreen().CenterPoint());
  generator->ClickLeftButton();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(status_area->IsMessageBubbleShown());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);

  // Click away from the shelf and unified system tray to hide the shelf.
  generator->MoveMouseTo(10, 10);
  generator->ClickLeftButton();
  EXPECT_TRUE(RunVisibilityUpdateForTrayCallback());
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());

  // Show the app list and the shelf should be temporarily visible.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Click on the unified system tray button, closing the app list.
  EXPECT_FALSE(status_area->IsMessageBubbleShown());
  generator->MoveMouseTo(
      status_area->unified_system_tray()->GetBoundsInScreen().CenterPoint());
  generator->ClickLeftButton();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(status_area->IsMessageBubbleShown());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);

  // Click on the ime menu tray button, to show a tray bubble and close the
  // unified system tray. The shelf should still be showing.
  generator->MoveMouseTo(
      status_area->ime_menu_tray()->GetBoundsInScreen().CenterPoint());
  generator->ClickLeftButton();
  EXPECT_FALSE(status_area->IsMessageBubbleShown());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Click away from the shelf and tray bubble to hide the shelf.
  generator->MoveMouseTo(10, 10);
  generator->ClickLeftButton();
  EXPECT_TRUE(RunVisibilityUpdateForTrayCallback());
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
}

// Tests the correct behavior of the shelf when there is a system modal window
// open when we have a single display.
TEST_F(ShelfLayoutManagerTest, ShelfWithSystemModalWindowSingleDisplay) {
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  window->Show();
  wm::ActivateWindow(window);

  // Enable system modal dialog, and make sure shelf is still hidden.
  ShellTestApi().SimulateModalWindowOpenForTest(true);
  EXPECT_TRUE(Shell::IsSystemModalWindowOpen());
  EXPECT_FALSE(wm::CanActivateWindow(window));
  Shelf::UpdateShelfVisibility();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Tests the correct behavior of the shelf when there is a system modal window
// open when we have dual display.
TEST_F(ShelfLayoutManagerTest, ShelfWithSystemModalWindowDualDisplay) {
  // Create two displays.
  UpdateDisplay("200x300,100x200");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  EXPECT_EQ(2U, root_windows.size());

  // Get the shelves in both displays and set them to be 'AutoHide'.
  Shelf* shelf_1 = Shelf::ForWindow(root_windows[0]);
  Shelf* shelf_2 = Shelf::ForWindow(root_windows[1]);
  EXPECT_NE(shelf_1, shelf_2);
  EXPECT_NE(shelf_1->GetWindow()->GetRootWindow(),
            shelf_2->GetWindow()->GetRootWindow());
  shelf_1->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  shelf_1->shelf_layout_manager()->LayoutShelf();
  shelf_2->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  shelf_2->shelf_layout_manager()->LayoutShelf();

  // Create a window in each display and show them in maximized state.
  aura::Window* window_1 = CreateTestWindowInParent(root_windows[0]);
  window_1->SetBounds(gfx::Rect(0, 0, 100, 100));
  window_1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  window_1->Show();
  aura::Window* window_2 = CreateTestWindowInParent(root_windows[1]);
  window_2->SetBounds(gfx::Rect(201, 0, 100, 100));
  window_2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  window_2->Show();

  EXPECT_EQ(shelf_1->GetWindow()->GetRootWindow(), window_1->GetRootWindow());
  EXPECT_EQ(shelf_2->GetWindow()->GetRootWindow(), window_2->GetRootWindow());
  EXPECT_TRUE(window_1->IsVisible());
  EXPECT_TRUE(window_2->IsVisible());

  // Enable system modal dialog, and make sure both shelves are still hidden.
  ShellTestApi().SimulateModalWindowOpenForTest(true);
  EXPECT_TRUE(Shell::IsSystemModalWindowOpen());
  EXPECT_FALSE(wm::CanActivateWindow(window_1));
  EXPECT_FALSE(wm::CanActivateWindow(window_2));
  Shelf::UpdateShelfVisibility();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_1->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_1->GetAutoHideState());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf_2->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_2->GetAutoHideState());
}

TEST_F(ShelfLayoutManagerTest, FullscreenWidgetHidesShelf) {
  Shelf* shelf = GetPrimaryShelf();
  // Create a normal window.
  views::Widget* widget = TestWidgetBuilder()
                              .SetBounds(gfx::Rect(11, 22, 300, 400))
                              .BuildOwnedByNativeWidget();
  ASSERT_FALSE(widget->IsFullscreen());

  // Shelf defaults to visible.
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Fullscreen window hides it.
  widget->SetFullscreen(true);
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());

  // Restoring the window restores it.
  widget->Restore();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Clean up.
  widget->Close();
}

// Tests that the shelf is only hidden for a fullscreen window at the front and
// toggles visibility when another window is activated.
TEST_F(ShelfLayoutManagerTest, FullscreenWindowInFrontHidesShelf) {
  Shelf* shelf = GetPrimaryShelf();
  EXPECT_EQ(WorkspaceWindowState::kDefault, GetWorkspaceWindowState());
  EXPECT_TRUE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  // Create a window and make it full screen.
  aura::Window* window1 = CreateTestWindow();
  window1->SetBounds(gfx::Rect(0, 0, 100, 100));
  window1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  window1->Show();
  EXPECT_EQ(WorkspaceWindowState::kFullscreen, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  aura::Window* window2 = CreateTestWindow();
  window2->SetBounds(gfx::Rect(0, 0, 100, 100));
  window2->Show();

  WindowState::Get(window1)->Activate();
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());

  WindowState::Get(window2)->Activate();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  WindowState::Get(window1)->Activate();
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
}

// Test the behavior of the shelf when a window on one display is fullscreen
// but the other display has the active window.
TEST_F(ShelfLayoutManagerTest, FullscreenWindowOnSecondDisplay) {
  UpdateDisplay("800x600,800x600");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();

  // Create windows on either display.
  aura::Window* window1 = CreateTestWindow();
  window1->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100),
                             display::Screen::GetScreen()->GetAllDisplays()[0]);
  window1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  window1->Show();

  aura::Window* window2 = CreateTestWindow();
  window2->SetBoundsInScreen(gfx::Rect(800, 0, 100, 100),
                             display::Screen::GetScreen()->GetAllDisplays()[1]);
  window2->Show();

  EXPECT_EQ(root_windows[0], window1->GetRootWindow());
  EXPECT_EQ(root_windows[1], window2->GetRootWindow());

  WindowState::Get(window2)->Activate();
  EXPECT_EQ(
      SHELF_HIDDEN,
      Shelf::ForWindow(window1)->shelf_layout_manager()->visibility_state());
  EXPECT_EQ(
      SHELF_VISIBLE,
      Shelf::ForWindow(window2)->shelf_layout_manager()->visibility_state());
}

// Test for Pinned mode.
TEST_F(ShelfLayoutManagerTest, PinnedWindowHidesShelf) {
  Shelf* shelf = GetPrimaryShelf();

  aura::Window* window1 = CreateTestWindow();
  window1->SetBounds(gfx::Rect(0, 0, 100, 100));
  window1->Show();

  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  window_util::PinWindow(window1, /* trusted */ false);
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());

  WindowState::Get(window1)->Restore();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
}

// Tests SHELF_ALIGNMENT_(LEFT, RIGHT).
TEST_F(ShelfLayoutManagerTest, SetAlignment) {
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  // Force an initial layout.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  shelf->SetAlignment(ShelfAlignment::kLeft);
  gfx::Rect shelf_bounds(GetShelfWidget()->GetWindowBoundsInScreen());
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  ASSERT_NE(-1, display.id());
  EXPECT_EQ(layout_manager->GetIdealBounds().width(),
            display.GetWorkAreaInsets().left());
  EXPECT_GE(shelf_bounds.width(),
            GetShelfWidget()->GetContentsView()->GetPreferredSize().width());
  EXPECT_EQ(ShelfAlignment::kLeft, GetPrimaryShelf()->alignment());
  EXPECT_EQ(display.work_area(),
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());

  StatusAreaWidget* status_area_widget = GetShelfWidget()->status_area_widget();
  gfx::Rect status_bounds(status_area_widget->GetWindowBoundsInScreen());
  // TODO(estade): Re-enable this check. See crbug.com/660928.
  //  EXPECT_GE(
  //      status_bounds.width(),
  //      status_area_widget->GetContentsView()->GetPreferredSize().width());
  EXPECT_EQ(layout_manager->GetIdealBounds().width(),
            display.GetWorkAreaInsets().left());
  EXPECT_EQ(0, display.GetWorkAreaInsets().top());
  EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
  EXPECT_EQ(0, display.GetWorkAreaInsets().right());
  EXPECT_EQ(display.bounds().x(), shelf_bounds.x());
  EXPECT_EQ(display.bounds().y(), shelf_bounds.y());
  EXPECT_EQ(display.bounds().height(), shelf_bounds.height());
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  display = display::Screen::GetScreen()->GetPrimaryDisplay();
  EXPECT_EQ(0, display.GetWorkAreaInsets().left());
  EXPECT_EQ(0, display.work_area().x());

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  shelf->SetAlignment(ShelfAlignment::kRight);
  shelf_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
  display = display::Screen::GetScreen()->GetPrimaryDisplay();
  ASSERT_NE(-1, display.id());
  EXPECT_EQ(layout_manager->GetIdealBounds().width(),
            display.GetWorkAreaInsets().right());
  EXPECT_GE(shelf_bounds.width(),
            GetShelfWidget()->GetContentsView()->GetPreferredSize().width());
  EXPECT_EQ(ShelfAlignment::kRight, GetPrimaryShelf()->alignment());
  EXPECT_EQ(display.work_area(),
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());

  status_bounds = gfx::Rect(status_area_widget->GetWindowBoundsInScreen());
  // TODO(estade): Re-enable this check. See crbug.com/660928.
  //  EXPECT_GE(
  //      status_bounds.width(),
  //      status_area_widget->GetContentsView()->GetPreferredSize().width());
  EXPECT_EQ(layout_manager->GetIdealBounds().width(),
            display.GetWorkAreaInsets().right());
  EXPECT_EQ(0, display.GetWorkAreaInsets().top());
  EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
  EXPECT_EQ(0, display.GetWorkAreaInsets().left());
  EXPECT_EQ(display.work_area().right(), shelf_bounds.x());
  EXPECT_EQ(display.bounds().y(), shelf_bounds.y());
  EXPECT_EQ(display.bounds().height(), shelf_bounds.height());

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

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  display = display::Screen::GetScreen()->GetPrimaryDisplay();
  EXPECT_EQ(0, display.GetWorkAreaInsets().right());
  EXPECT_EQ(0, display.bounds().right() - display.work_area().right());
  EXPECT_EQ(stable_work_area,
            GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());
}

// Verifies that the shelf looks the way it should after an alignment change.
// See crbug/1051824 .
TEST_F(ShelfLayoutManagerTest, ShelfWidgetLayoutUpdatedAfterAlignmentChange) {
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  ShelfWidget* shelf_widget = shelf->shelf_widget();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  shelf->SetAlignment(ShelfAlignment::kLeft);
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  gfx::Rect opaque_background_bounds =
      shelf->shelf_widget()->GetOpaqueBackground()->bounds();
  ::wm::ConvertRectToScreen(shelf_widget->GetNativeWindow(),
                            &opaque_background_bounds);
  int cross_axis_visible_pixels =
      opaque_background_bounds.right() - display::Screen::GetScreen()
                                             ->GetPrimaryDisplay()
                                             .bounds()
                                             .left_center()
                                             .x();
  EXPECT_EQ(ShelfConfig::Get()->shelf_size(), cross_axis_visible_pixels);

  shelf->SetAlignment(ShelfAlignment::kRight);
  layout_manager->LayoutShelf();
  opaque_background_bounds =
      shelf->shelf_widget()->GetOpaqueBackground()->bounds();
  ::wm::ConvertRectToScreen(shelf_widget->GetNativeWindow(),
                            &opaque_background_bounds);
  cross_axis_visible_pixels =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds().right() -
      opaque_background_bounds.left_center().x();
  EXPECT_EQ(ShelfConfig::Get()->shelf_size(), cross_axis_visible_pixels);
}

// Tests swipe gestures in a various of shelf alignments and shelf auto hide
// configurations.
TEST_F(ShelfLayoutManagerTest, GestureDrag) {
  // Slop is an implementation detail of gesture recognition, and complicates
  // these tests. Ignore it.
  ui::GestureConfiguration::GetInstance()
      ->set_max_touch_move_in_pixels_for_click(0);
  Shelf* shelf = GetPrimaryShelf();
  {
    SCOPED_TRACE("BOTTOM");
    shelf->SetAlignment(ShelfAlignment::kBottom);
    gfx::Rect shelf_bounds = GetVisibleShelfWidgetBoundsInScreen();
    gfx::Point bottom_center = shelf_bounds.bottom_center();
    bottom_center.Offset(0, -1);  // Make sure the point is inside shelf.
    RunGestureDragTests(bottom_center, shelf_bounds.top_center());
    GetAppListTestHelper()->WaitUntilIdle();
  }
  {
    SCOPED_TRACE("LEFT");
    shelf->SetAlignment(ShelfAlignment::kLeft);
    gfx::Rect shelf_bounds = GetVisibleShelfWidgetBoundsInScreen();
    gfx::Point right_center = shelf_bounds.right_center();
    right_center.Offset(-1, 0);  // Make sure the point is inside shelf.
    RunGestureDragTests(shelf_bounds.left_center(), right_center);
    GetAppListTestHelper()->WaitUntilIdle();
  }
  {
    SCOPED_TRACE("RIGHT");
    shelf->SetAlignment(ShelfAlignment::kRight);
    gfx::Rect shelf_bounds = GetVisibleShelfWidgetBoundsInScreen();
    gfx::Point right_center = shelf_bounds.right_center();
    right_center.Offset(-1, 0);  // Make sure the point is inside shelf.
    RunGestureDragTests(right_center, shelf_bounds.left_center());
    GetAppListTestHelper()->WaitUntilIdle();
  }
}

// Tests that the shelf does not "overscroll", that is, dragging the shelf in
// does not bring it past its ideal bounds.
TEST_F(ShelfLayoutManagerTest, ShelfDoesNotOverscrollDuringGestureDragIn) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();

  // Note: A window must be visible in order to hide the shelf.
  CreateTestWidget();
  {
    SCOPED_TRACE("BOTTOM");
    shelf->SetAlignment(ShelfAlignment::kBottom);
    gfx::Rect ideal_bounds = layout_manager->GetIdealBounds();
    ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

    // Scroll past the shelf's ideal bounds.
    gfx::Point start = GetPrimaryDisplay().bounds().bottom_center();
    StartScroll(start);
    UpdateScroll(gfx::Vector2d(0, -ideal_bounds.height() - 50));

    // The shelf should not extend past its ideal bounds.
    EXPECT_EQ(ideal_bounds, shelf->GetShelfBoundsInScreen());

    // End the scroll so as not to interfere with future tests.
    EndScroll(/*is_fling=*/false, 0.f);
  }
  {
    SCOPED_TRACE("LEFT");
    shelf->SetAlignment(ShelfAlignment::kLeft);
    gfx::Rect ideal_bounds = layout_manager->GetIdealBounds();
    ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

    // Scroll past the shelf's ideal bounds.
    gfx::Point start = GetPrimaryDisplay().bounds().left_center();
    StartScroll(start);
    UpdateScroll(gfx::Vector2d(ideal_bounds.width() + 50, 0));

    EXPECT_EQ(ideal_bounds, shelf->GetShelfBoundsInScreen());

    // End the scroll so as not to interfere with future tests.
    EndScroll(/*is_fling=*/false, 0.f);
  }
  {
    SCOPED_TRACE("RIGHT");
    shelf->SetAlignment(ShelfAlignment::kRight);
    gfx::Rect ideal_bounds = layout_manager->GetIdealBounds();
    ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

    // Scroll past the shelf's ideal bounds.
    gfx::Point start = GetPrimaryDisplay().bounds().right_center();
    StartScroll(start);
    UpdateScroll(gfx::Vector2d(-ideal_bounds.width() - 50, 0));

    EXPECT_EQ(ideal_bounds, shelf->GetShelfBoundsInScreen());

    // End the scroll so as not to interfere with future tests.
    EndScroll(/*is_fling=*/false, 0.f);
  }
}

// Swiping on shelf when fullscreen app list is opened should have no effect.
TEST_F(ShelfLayoutManagerTest, SwipingOnShelfIfAppListOpened) {
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->OnAppListVisibilityChanged(true, GetPrimaryDisplayId());
  EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Note: A window must be visible in order to hide the shelf.
  CreateTestWidget();

  ui::test::EventGenerator* generator = GetEventGenerator();
  constexpr base::TimeDelta kTimeDelta = base::Milliseconds(100);
  constexpr int kNumScrollSteps = 4;
  gfx::Point start = GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();

  // Swiping down on shelf when the fullscreen app list is opened
  // should not hide the shelf.
  gfx::Point end = start + gfx::Vector2d(0, 120);
  generator->GestureScrollSequence(start, end, kTimeDelta, kNumScrollSteps);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  // Swiping left on shelf when the fullscreen app list is opened
  // should not hide the shelf.
  shelf->SetAlignment(ShelfAlignment::kLeft);
  end = start + gfx::Vector2d(-120, 0);
  generator->GestureScrollSequence(start, end, kTimeDelta, kNumScrollSteps);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  // Swiping right on shelf when the fullscreen app list is opened
  // should not hide the shelf.
  shelf->SetAlignment(ShelfAlignment::kRight);
  end = start + gfx::Vector2d(120, 0);
  generator->GestureScrollSequence(start, end, kTimeDelta, kNumScrollSteps);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());
}

TEST_F(ShelfLayoutManagerTest, WindowVisibilityDisablesAutoHide) {
  UpdateDisplay("800x600,800x600");
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  // Create a visible window so auto-hide behavior is enforced
  views::Widget* dummy = CreateTestWidget();

  // Window visible => auto hide behaves normally.
  layout_manager->UpdateVisibilityState(/*force_layout=*/false);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Window minimized => auto hide disabled.
  dummy->Minimize();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Window closed => auto hide disabled.
  dummy->CloseNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Multiple window test
  views::Widget* window1 = CreateTestWidget();
  views::Widget* window2 = CreateTestWidget();

  // both visible => normal autohide
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // either minimzed => normal autohide
  window2->Minimize();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  window2->Restore();
  window1->Minimize();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // both minimized => disable auto hide
  window2->Minimize();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Test moving windows to/from other display.
  window2->Restore();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  // Move to second display.
  window2->SetBounds(gfx::Rect(850, 50, 50, 50));
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  // Move back to primary display.
  window2->SetBounds(gfx::Rect(50, 50, 50, 50));
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Tests the shelf animates back to its original visible bounds when it is
// dragged down but there are no visible windows.
TEST_F(ShelfLayoutManagerTest,
       ShelfAnimatesWhenGestureCompleteNoVisibleWindow) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  gfx::Rect visible_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
  {
    // Enable animations so that we can make sure that they occur.
    ui::ScopedAnimationDurationScaleMode regular_animations(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

    ui::test::EventGenerator* generator = GetEventGenerator();
    gfx::Rect shelf_bounds_in_screen =
        GetShelfWidget()->GetWindowBoundsInScreen();
    gfx::Point start(shelf_bounds_in_screen.CenterPoint());
    gfx::Point end(start.x(), shelf_bounds_in_screen.bottom());
    views::WidgetAnimationWaiter waiter(GetShelfWidget(), visible_bounds);
    generator->GestureScrollSequence(start, end, base::Milliseconds(10), 5);
    EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
    EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

    // Wait for the animation to complete and check that it was valid.
    waiter.WaitForAnimation();
    EXPECT_TRUE(waiter.WasValidAnimation());
  }
}

// Tests that the shelf animates to the visible bounds after a swipe up on
// the auto hidden shelf.
TEST_F(ShelfLayoutManagerTest, ShelfAnimatesToVisibleWhenGestureInComplete) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  gfx::Rect visible_bounds = GetShelfWidget()->GetWindowBoundsInScreen();

  // Create a visible window, otherwise the shelf will not hide.
  CreateTestWidget();

  // Get the bounds of the shelf when it is hidden.
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  {
    // Enable the animations so that we can make sure they do occur.
    ui::ScopedAnimationDurationScaleMode regular_animations(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

    display::Display display =
        display::Screen::GetScreen()->GetPrimaryDisplay();
    gfx::Point start = display.bounds().bottom_center();
    gfx::Point end(
        start.x(),
        start.y() - (Shell::Get()->shelf_config()->shelf_size() - 1));
    ui::test::EventGenerator* generator = GetEventGenerator();

    views::WidgetAnimationWaiter waiter(GetShelfWidget(), visible_bounds);
    generator->GestureScrollSequence(start, end, base::Milliseconds(10), 1);
    EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
    EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
    waiter.WaitForAnimation();
    EXPECT_TRUE(waiter.WasValidAnimation());
  }
}

// Tests that the shelf hide immediately without animation after a swipe down
// on the visible shelf to the bottom of the display bounds.
TEST_F(ShelfLayoutManagerTest,
       ShelfHidesImmediatelyWhenGestureOutCompleteBelowTargetBounds) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Create a visible window, otherwise the shelf will not hide.
  CreateTestWidget();

  gfx::Rect auto_hidden_bounds = GetShelfWidget()->GetWindowBoundsInScreen();

  {
    // Enable the animations so that we can make sure they do occur.
    ui::ScopedAnimationDurationScaleMode regular_animations(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

    ui::test::EventGenerator* generator = GetEventGenerator();

    // Show the shelf first.
    SwipeUpOnShelf();
    EXPECT_FALSE(GetShelfWidget()->GetLayer()->GetAnimator()->is_animating());
    EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

    gfx::Point start = GetShelfWidget()->GetWindowBoundsInScreen().top_center();

    // Set the endpoint below the display bounds of the shelf widget.
    gfx::Point endpoint_below_target(start.x(), start.y() + 100);

    // Now swipe down to the endpoint.
    generator->GestureScrollSequence(start, endpoint_below_target,
                                     base::Milliseconds(10), 1);
    EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
    EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

    // Check that the shelf widget is moved back to the auto hidden bounds
    // without animating.
    EXPECT_FALSE(GetShelfWidget()->GetLayer()->GetAnimator()->is_animating());
    EXPECT_EQ(auto_hidden_bounds, GetShelfWidget()->GetWindowBoundsInScreen());
  }
}

// Tests that the shelf animates to the auto hidden bounds after a swipe down
// on the visible shelf to a point above the auto hidden bounds.
TEST_F(ShelfLayoutManagerTest,
       ShelfAnimatesToHiddenWhenGestureOutCompleteAboveTargetBounds) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Create a visible window, otherwise the shelf will not hide.
  CreateTestWidget();

  gfx::Rect auto_hidden_bounds = GetShelfWidget()->GetWindowBoundsInScreen();

  {
    // Enable the animations so that we can make sure they do occur.
    ui::ScopedAnimationDurationScaleMode regular_animations(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

    ui::test::EventGenerator* generator = GetEventGenerator();

    // Show the shelf first.
    SwipeUpOnShelf();
    EXPECT_FALSE(GetShelfWidget()->GetLayer()->GetAnimator()->is_animating());
    EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

    gfx::Point start = GetShelfWidget()->GetWindowBoundsInScreen().top_center();

    // Set the end point to a point above the target bounds which is
    // hidden_shelf_in_screen_portion pixels high.
    const gfx::Rect display_bounds =
        display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
    gfx::Point endpoint_above_target = gfx::Point(
        display_bounds.bottom_center().x(),
        display_bounds.bottom_center().y() -
            ShelfConfig::Get()->hidden_shelf_in_screen_portion() - 1);

    // Now swipe down to the endpoint.
    views::WidgetAnimationWaiter waiter2(GetShelfWidget(), auto_hidden_bounds);
    generator->GestureScrollSequence(start, endpoint_above_target,
                                     base::Milliseconds(10), 1);
    EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
    EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
    waiter2.WaitForAnimation();

    // Check if the shelf widget is animated to the auto hidden bounds after the
    // drag.
    EXPECT_TRUE(waiter2.WasValidAnimation());
  }
}

TEST_F(ShelfLayoutManagerTest, AutohideShelfForAutohideWhenActiveWindow) {
  Shelf* shelf = GetPrimaryShelf();

  views::Widget* widget_one = CreateTestWidget();
  views::Widget* widget_two = CreateTestWidget();
  aura::Window* window_two = widget_two->GetNativeWindow();

  // Turn on hide_shelf_when_active behavior for window two - shelf should
  // still be visible when window two is made active since it is not yet
  // maximized.
  widget_one->Activate();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  WindowState::Get(window_two)
      ->set_autohide_shelf_when_maximized_or_fullscreen(true);
  widget_two->Activate();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Now the flag takes effect once window two is maximized.
  widget_two->Maximize();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());

  // The hide_shelf_when_active flag should override the behavior of the
  // hide_shelf_when_fullscreen flag even if the window is currently fullscreen.
  WindowState::Get(window_two)->SetHideShelfWhenFullscreen(false);
  widget_two->SetFullscreen(true);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  WindowState::Get(window_two)->Restore();

  // With the flag off, shelf no longer auto-hides.
  widget_one->Activate();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  WindowState::Get(window_two)
      ->set_autohide_shelf_when_maximized_or_fullscreen(false);
  widget_two->Activate();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  WindowState::Get(window_two)
      ->set_autohide_shelf_when_maximized_or_fullscreen(true);
  window_two->SetProperty(aura::client::kZOrderingKey,
                          ui::ZOrderLevel::kFloatingWindow);

  auto* shelf_window = shelf->GetWindow();
  aura::Window* container = shelf_window->GetRootWindow()->GetChildById(
      kShellWindowId_AlwaysOnTopContainer);
  EXPECT_TRUE(base::Contains(container->children(), window_two));

  widget_two->Maximize();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());

  EXPECT_EQ(WorkspaceWindowState::kMaximized, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());
}

TEST_F(ShelfLayoutManagerTest, ShelfFlickerOnTrayActivation) {
  Shelf* shelf = GetPrimaryShelf();

  // Create a visible window so auto-hide behavior is enforced.
  CreateTestWidget();

  // Turn on auto-hide for the shelf.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Show the status menu. That should make the shelf visible again.
  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
      AcceleratorAction::kToggleSystemTrayBubble, {});
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());
}

TEST_F(ShelfLayoutManagerTest, WorkAreaChangeWorkspace) {
  // Make sure the shelf is always visible.
  Shelf* shelf = GetPrimaryShelf();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  layout_manager->LayoutShelf();

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

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

  // Both windows are maximized. They should be of the same size.
  EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
            widget_two->GetNativeWindow()->bounds().ToString());
  int area_when_shelf_shown =
      widget_one->GetNativeWindow()->bounds().size().GetArea();

  // Now hide the shelf.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  // Both windows should be resized according to the shelf status.
  EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
            widget_two->GetNativeWindow()->bounds().ToString());
  // Resized to small.
  EXPECT_LT(area_when_shelf_shown,
            widget_one->GetNativeWindow()->bounds().size().GetArea());

  // Now show the shelf.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);

  // Again both windows should be of the same size.
  EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
            widget_two->GetNativeWindow()->bounds().ToString());
  EXPECT_EQ(area_when_shelf_shown,
            widget_one->GetNativeWindow()->bounds().size().GetArea());
}

TEST_F(ShelfLayoutManagerTest, BackgroundTypeWhenLockingScreen) {
  // Creates a maximized window to have a background type other than default.
  std::unique_ptr<aura::Window> window(CreateTestWindow());
  window->Show();
  wm::ActivateWindow(window.get());
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            GetShelfLayoutManager()->shelf_background_type());

  Shell::Get()->lock_state_controller()->LockWithoutAnimation();
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());
}

TEST_F(ShelfLayoutManagerTest, WorkspaceMask) {
  std::unique_ptr<aura::Window> w1(CreateTestWindow());
  w1->Show();
  EXPECT_EQ(WorkspaceWindowState::kDefault, GetWorkspaceWindowState());
  EXPECT_TRUE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  // Overlaps with shelf should not cause any specific behavior.
  w1->SetBounds(GetShelfLayoutManager()->GetIdealBounds());
  EXPECT_EQ(WorkspaceWindowState::kDefault, GetWorkspaceWindowState());
  EXPECT_TRUE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(WorkspaceWindowState::kMaximized, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_EQ(WorkspaceWindowState::kFullscreen, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  std::unique_ptr<aura::Window> w2(CreateTestWindow());
  w2->Show();
  EXPECT_EQ(WorkspaceWindowState::kDefault, GetWorkspaceWindowState());
  EXPECT_TRUE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  w2.reset();
  EXPECT_EQ(WorkspaceWindowState::kFullscreen, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_EQ(WorkspaceWindowState::kDefault, GetWorkspaceWindowState());
  EXPECT_TRUE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());
}

TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColor) {
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());

  std::unique_ptr<aura::Window> w1(CreateTestWindow());
  w1->Show();
  wm::ActivateWindow(w1.get());
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            GetShelfLayoutManager()->shelf_background_type());

  std::unique_ptr<aura::Window> w2(CreateTestWindow());
  w2->Show();
  wm::ActivateWindow(w2.get());
  // Overlaps with shelf.
  w2->SetBounds(GetShelfLayoutManager()->GetIdealBounds());

  // Still background is 'maximized'.
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            GetShelfLayoutManager()->shelf_background_type());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            GetShelfLayoutManager()->shelf_background_type());

  std::unique_ptr<aura::Window> w3(CreateTestWindow());
  w3->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kWindow);
  ::wm::AddTransientChild(w1.get(), w3.get());
  w3->Show();
  wm::ActivateWindow(w3.get());

  EXPECT_EQ(WorkspaceWindowState::kMaximized, GetWorkspaceWindowState());
  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());

  w3.reset();
  w1.reset();
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());
}

// Tests that the shelf background gets updated when the AppList stays open
// during the tablet mode transition with a visible window.
TEST_F(ShelfLayoutManagerTest, TabletModeTransitionWithAppListVisible) {
  // Home Launcher requires an internal display.
  display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
      .SetFirstDisplayAsInternalDisplay();

  // Show a window, which will later fill the whole screen.
  std::unique_ptr<aura::Window> window(
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400)));
  window->SetProperty(aura::client::kResizeBehaviorKey,
                      aura::client::kResizeBehaviorCanResize |
                          aura::client::kResizeBehaviorCanMaximize);
  wm::ActivateWindow(window.get());

  // Show the AppList over |window|.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());

  // Transition to tablet mode.
  TabletModeControllerTestApi().EnterTabletMode();

  // |window| should be maximized, and the shelf background should match the
  // maximized state.
  EXPECT_EQ(WorkspaceWindowState::kMaximized, GetWorkspaceWindowState());
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            GetShelfLayoutManager()->shelf_background_type());

  // Hiding the window should exit app mode.
  window->Hide();
  EXPECT_EQ(ShelfBackgroundType::kHomeLauncher,
            GetShelfLayoutManager()->shelf_background_type());
}

// Verify that the auto-hide shelf has default background by default and still
// has the default background when a window is maximized in clamshell mode.
TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColorAutoHide) {
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());

  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  std::unique_ptr<aura::Window> w1(CreateTestWindow());
  w1->Show();
  wm::ActivateWindow(w1.get());
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());
}

// Verify that the shelf has a maximized background when a window is in the
// fullscreen state.
TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColorFullscreen) {
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());

  std::unique_ptr<aura::Window> w1(CreateTestWindow());
  w1->Show();
  wm::ActivateWindow(w1.get());
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            GetShelfLayoutManager()->shelf_background_type());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            GetShelfLayoutManager()->shelf_background_type());
}

// Verify the hit bounds of the status area extend to the edge of the shelf.
TEST_F(ShelfLayoutManagerTest, StatusAreaHitBoxCoversEdge) {
  StatusAreaWidget* status_area_widget = GetShelfWidget()->status_area_widget();
  ui::test::EventGenerator* generator = GetEventGenerator();
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  gfx::Rect inset_display_bounds = display.bounds();
  inset_display_bounds.Inset(gfx::Insets::TLBR(0, 0, 1, 1));

  // Test bottom right pixel for bottom alignment.
  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kBottom);
  generator->MoveMouseTo(inset_display_bounds.bottom_right());
  EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
  generator->ClickLeftButton();
  EXPECT_TRUE(status_area_widget->IsMessageBubbleShown());
  generator->ClickLeftButton();
  EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());

  // Test bottom right pixel for right alignment.
  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kRight);
  generator->MoveMouseTo(inset_display_bounds.bottom_right());
  EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
  generator->ClickLeftButton();
  EXPECT_TRUE(status_area_widget->IsMessageBubbleShown());
  generator->ClickLeftButton();
  EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());

  // Test bottom left pixel for left alignment.
  generator->MoveMouseTo(inset_display_bounds.bottom_left());
  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
  EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
  generator->ClickLeftButton();
  EXPECT_TRUE(status_area_widget->IsMessageBubbleShown());
  generator->ClickLeftButton();
  EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
}

// Tests that when the auto-hide behaviour is changed during an animation the
// target bounds are updated to reflect the new state.
TEST_F(ShelfLayoutManagerTest,
       ShelfAutoHideToggleDuringAnimationUpdatesBounds) {
  aura::Window* status_window =
      GetShelfWidget()->status_area_widget()->GetNativeView();
  gfx::Rect initial_bounds = status_window->bounds();

  ui::ScopedAnimationDurationScaleMode regular_animations(
      ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlwaysHidden);
  gfx::Rect hide_target_bounds = status_window->GetTargetBounds();
  EXPECT_GT(hide_target_bounds.y(), initial_bounds.y());

  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  gfx::Rect reshow_target_bounds = status_window->GetTargetBounds();
  EXPECT_EQ(initial_bounds, reshow_target_bounds);
}

// Tests that during shutdown, that window activation changes are properly
// handled, and do not crash (crbug.com/458768)
TEST_F(ShelfLayoutManagerTest, ShutdownHandlesWindowActivation) {
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  aura::Window* window1 = CreateTestWindowInShellWithId(0);
  window1->SetBounds(gfx::Rect(0, 0, 100, 100));
  window1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  window1->Show();
  std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(0));
  window2->SetBounds(gfx::Rect(0, 0, 100, 100));
  window2->Show();
  wm::ActivateWindow(window1);

  GetShelfLayoutManager()->PrepareForShutdown();

  // Deleting a focused maximized window will switch focus to |window2|. This
  // would normally cause the ShelfLayoutManager to update its state. However
  // during shutdown we want to handle this without crashing.
  delete window1;
}

TEST_F(ShelfLayoutManagerTest, ShelfLayoutInUnifiedDesktop) {
  Shell::Get()->display_manager()->SetUnifiedDesktopEnabled(true);
  UpdateDisplay("500x400, 500x400");

  // When the unified desktop is enabled, UpdateDisplay() adds a display so the
  // shelf is recreated. Therefore, update the shelf related data members.
  UpdateShelfRelatedMembers();

  StatusAreaWidget* status_area_widget = GetShelfWidget()->status_area_widget();
  EXPECT_TRUE(status_area_widget->IsVisible());
  // Shelf should be in the first display's area.
  gfx::Rect status_area_bounds(status_area_widget->GetWindowBoundsInScreen());
  EXPECT_TRUE(gfx::Rect(0, 0, 500, 400).Contains(status_area_bounds));
  EXPECT_EQ(gfx::Point(500, 400), status_area_bounds.bottom_right());
}

// Tests that tapping the home button is successful on the autohidden shelf.
TEST_F(ShelfLayoutManagerTest, PressHomeButtonOnAutoHideShelf) {
  // Enable accessibility feature that forces home button to be shown even with
  // kHideShelfControlsInTabletMode enabled.
  Shell::Get()
      ->accessibility_controller()
      ->SetTabletModeShelfNavigationButtonsEnabled(true);
  TabletModeControllerTestApi().EnterTabletMode();
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  const display::Display display =
      display::Screen::GetScreen()->GetPrimaryDisplay();

  // Create a window to hide the shelf in auto-hide mode.
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());

  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);

  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);

  ShelfNavigationWidget::TestApi navigation_test_api(
      shelf->navigation_widget());
  ASSERT_TRUE(navigation_test_api.IsHomeButtonVisible());
  // Wait for the back button to finish animating from behind the home button.
  ShelfViewTestAPI(GetPrimaryShelf()->GetShelfViewForTesting())
      .RunMessageLoopUntilAnimationsDone(
          navigation_test_api.GetBoundsAnimator());

  // Press the home button with touch.
  GetEventGenerator()->GestureTapAt(shelf->shelf_widget()
                                        ->navigation_widget()
                                        ->GetHomeButton()
                                        ->GetBoundsInScreen()
                                        .CenterPoint());

  // The app list should now be visible, and the window we created should hide.
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_FALSE(window->IsVisible());
}

// Tests that the auto-hide shelf has expected behavior when pressing the
// AppList button while the shelf is being dragged by gesture (see
// https://crbug.com/953877).
TEST_F(ShelfLayoutManagerTest, PressHomeBtnWhenAutoHideShelfBeingDragged) {
  // Create a widget to hide the shelf in auto-hide mode.
  CreateTestWidget();
  GetPrimaryShelf()->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_FALSE(GetPrimaryShelf()->IsVisible());

  // Emulate to drag the shelf to show it.
  gfx::Point gesture_location = display::Screen::GetScreen()
                                    ->GetPrimaryDisplay()
                                    .bounds()
                                    .bottom_center();
  int delta_y = -1;
  base::TimeTicks timestamp = base::TimeTicks::Now();

  ui::GestureEvent start_event = ui::GestureEvent(
      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin, 0, delta_y));
  GetShelfLayoutManager()->ProcessGestureEventOfAutoHideShelf(
      &start_event, GetShelfWidget()->GetNativeView());
  gesture_location.Offset(0, delta_y);

  // Ensure that Shelf is higher than the default height, required by the bug
  // reproduction procedures.
  delta_y = -ShelfConfig::Get()->shelf_size() - 1;

  timestamp += base::Milliseconds(200);
  ui::GestureEvent update_event = ui::GestureEvent(
      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
      ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate, 0, delta_y));
  GetShelfLayoutManager()->ProcessGestureEventOfAutoHideShelf(
      &update_event, GetShelfWidget()->GetNativeView());

  // Emulate to press the AppList button while dragging the Shelf.
  PressHomeButton();
  EXPECT_TRUE(GetPrimaryShelf()->IsVisible());

  // Release the press.
  delta_y -= 1;
  gesture_location.Offset(0, delta_y);
  timestamp += base::Milliseconds(200);
  ui::GestureEvent scroll_end_event = ui::GestureEvent(
      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));
  GetShelfLayoutManager()->ProcessGestureEventOfAutoHideShelf(
      &scroll_end_event, GetShelfWidget()->GetNativeView());
  ui::GestureEvent gesture_end_event = ui::GestureEvent(
      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
      ui::GestureEventDetails(ui::EventType::kGestureEnd));
  GetShelfLayoutManager()->ProcessGestureEventOfAutoHideShelf(
      &gesture_end_event, GetShelfWidget()->GetNativeView());

  // Press the AppList button to hide the AppList and Shelf. Check the following
  // things:
  // (1) Shelf is hidden
  // (2) Shelf has correct bounds in screen coordinates.
  PressHomeButton();
  EXPECT_EQ(
      GetScreenAvailableBounds().bottom_left() +
          gfx::Point(0, -ShelfConfig::Get()->hidden_shelf_in_screen_portion())
              .OffsetFromOrigin(),
      GetPrimaryShelf()->shelf_widget()->GetWindowBoundsInScreen().origin());
  EXPECT_FALSE(GetPrimaryShelf()->IsVisible());
}

// Tests that the shelf has expected bounds when dragging the shelf by gesture
// and pressing the AppList button by mouse during drag (see
// https://crbug.com/968768).
TEST_F(ShelfLayoutManagerTest, MousePressAppListBtnWhenShelfBeingDragged) {
  // Drag the shelf upward. Notice that in order to drag shelf instead of
  // AppList from shelf, we need to drag the shelf downward a little bit then
  // upward. Because the bug is related with RootView, the event should be sent
  // through the shelf widget.
  gfx::Point gesture_location =
      GetPrimaryShelf()->GetShelfViewForTesting()->bounds().CenterPoint();
  int delta_y = 1;
  base::TimeTicks timestamp = base::TimeTicks::Now();
  ui::GestureEvent start_event = ui::GestureEvent(
      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin, 0, delta_y));
  GetPrimaryShelf()->shelf_widget()->OnGestureEvent(&start_event);
  delta_y = -5;
  timestamp += base::Milliseconds(200);
  ui::GestureEvent update_event = ui::GestureEvent(
      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
      ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate, 0, delta_y));
  GetPrimaryShelf()->shelf_widget()->OnGestureEvent(&update_event);

  // Press the AppList button by mouse.
  views::View* home_button =
      GetPrimaryShelf()->navigation_widget()->GetHomeButton();
  GetEventGenerator()->MoveMouseTo(
      home_button->GetBoundsInScreen().CenterPoint());
  GetEventGenerator()->ClickLeftButton();

  // End the gesture event.
  delta_y -= 1;
  gesture_location.Offset(0, delta_y);
  timestamp += base::Milliseconds(200);
  ui::GestureEvent scroll_end_event = ui::GestureEvent(
      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));
  GetPrimaryShelf()->shelf_widget()->OnGestureEvent(&scroll_end_event);

  // Verify that the shelf has expected bounds.
  EXPECT_EQ(
      GetScreenAvailableBounds().bottom_left() +
          gfx::Point(0, -ShelfConfig::Get()->shelf_size()).OffsetFromOrigin(),
      GetPrimaryShelf()->shelf_widget()->GetWindowBoundsInScreen().origin());
}

// Tests that tap outside of the AUTO_HIDE_SHOWN shelf should hide it.
TEST_F(ShelfLayoutManagerTest, TapOutsideOfAutoHideShownShelf) {
  views::Widget* widget = CreateTestWidget();
  Shelf* shelf = GetPrimaryShelf();
  ui::test::EventGenerator* generator = GetEventGenerator();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  aura::Window* window = widget->GetNativeWindow();
  gfx::Rect window_bounds = window->GetBoundsInScreen();
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Tap outside the window and AUTO_HIDE_SHOWN shelf should hide the shelf.
  gfx::Point tap_location =
      window_bounds.bottom_right() + gfx::Vector2d(10, 10);
  generator->GestureTapAt(tap_location);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  SwipeUpOnShelf();

  // Tap inside the AUTO_HIDE_SHOWN shelf should not hide the shelf.
  gfx::Rect shelf_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
  tap_location = gfx::Point(shelf_bounds.CenterPoint());
  generator->GestureTapAt(tap_location);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Tap inside the window should still hide the shelf.
  tap_location = window_bounds.origin() + gfx::Vector2d(10, 10);
  generator->GestureTapAt(tap_location);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  SwipeUpOnShelf();

  // Tap the system tray that inside the status area should not hide the shelf
  // but open the systrem tray bubble.
  generator->GestureTapAt(
      GetPrimaryUnifiedSystemTray()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());
}

// Tests that swiping up on the AUTO_HIDE_HIDDEN shelf, with various speeds,
// offsets, and angles, always shows the shelf.
TEST_F(ShelfLayoutManagerTest, SwipeUpAutoHideHiddenShelf) {
  ui::test::EventGenerator* generator = GetEventGenerator();
  Shelf* shelf = GetPrimaryShelf();

  // Create a window so that the shelf will hide.
  const aura::Window* window = CreateTestWidget()->GetNativeWindow();
  const gfx::Point tap_to_hide_shelf_location =
      window->GetBoundsInScreen().CenterPoint();
  const gfx::Rect display_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  const int time_deltas[] = {10, 50, 100, 500};
  const int num_scroll_steps[] = {2, 5, 10, 50};
  const int x_offsets[] = {10, 20, 50};
  const int y_offsets[] = {70, 100, 300, 500};

  for (int time_delta : time_deltas) {
    for (int scroll_steps : num_scroll_steps) {
      for (int x_offset : x_offsets) {
        for (int y_offset : y_offsets) {
          const gfx::Point start(display_bounds.bottom_center());
          const gfx::Point end(start + gfx::Vector2d(x_offset, -y_offset));
          generator->GestureScrollSequence(
              start, end, base::Milliseconds(time_delta), scroll_steps);
          EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState())
              << "Failure to show shelf after a swipe up in " << time_delta
              << "ms, " << scroll_steps << " steps, " << x_offset
              << " X-offset and " << y_offset << " Y-offset.";
          generator->GestureTapAt(tap_to_hide_shelf_location);
          EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
        }
      }
    }
  }
}

// Tests the auto-hide shelf status when moving the mouse in and out.
TEST_F(ShelfLayoutManagerTest, AutoHideShelfOnMouseMove) {
  // Create one window, or the shelf won't auto-hide.
  CreateTestWidget();
  Shelf* shelf = GetPrimaryShelf();
  ui::test::EventGenerator* generator = GetEventGenerator();
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();

  // Set the shelf to auto-hide.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Swipe up to show the shelf.
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Move the mouse far away from the shelf, but without having been on the
  // shelf first. This isn't technically a mouse-out event, so the shelf should
  // not hide.
  generator->MoveMouseTo(0, 0);
  ASSERT_FALSE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Now place the mouse on the shelf, then move away. The shelf should hide.
  generator->MoveMouseTo(1, display.bounds().bottom() - 1);
  generator->MoveMouseTo(0, 0);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Now let's show the shelf again.
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Move the mouse away, but move it back within the shelf immediately. The
  // shelf should remain shown.
  generator->MoveMouseTo(0, 0);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  generator->MoveMouseTo(1, display.bounds().bottom() - 1);
  ASSERT_FALSE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
}

// Verifies that after showing the system tray by shortcut, the shelf item still
// responds to the gesture event. (see https://crbug.com/921182)
TEST_F(ShelfLayoutManagerTest, ShelfItemRespondToGestureEvent) {
  // Prepare for the auto-hide shelf test.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  Shelf* shelf = GetPrimaryShelf();
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(0, 0);

  ShelfTestUtil::AddAppShortcut("app_id", TYPE_APP);
  ShelfViewTestAPI(GetPrimaryShelf()->GetShelfViewForTesting())
      .RunMessageLoopUntilAnimationsDone();

  // Turn on the auto-hide mode for shelf. Check the initial states.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Show the system tray with the shortcut. Expect that the shelf is shown
  // after triggering the accelerator.
  PressAndReleaseKey(ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Tap on the shelf button. Expect that the shelf button responds to gesture
  // events.
  base::UserActionTester user_action_tester;
  user_action_tester.ResetCounts();
  views::View* button = GetPrimaryShelf()
                            ->GetShelfViewForTesting()
                            ->first_visible_button_for_testing();
  gfx::Point shelf_btn_center = button->GetBoundsInScreen().CenterPoint();
  generator->GestureTapAt(shelf_btn_center);
  EXPECT_EQ(1, user_action_tester.GetActionCount("Launcher_ClickOnApp"));
}

// Tests the auto-hide shelf status with mouse events.
TEST_F(ShelfLayoutManagerTest, AutoHideShelfOnMouseEvents) {
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  Shelf* shelf = GetPrimaryShelf();
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(0, 0);

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Swipe up to show the auto-hide shelf.
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Move the mouse should not hide the AUTO_HIDE_SHOWN shelf immediately.
  generator->MoveMouseTo(5, 5);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Mouse press outside the shelf should hide the AUTO_HIDE_SHOWN shelf.
  generator->PressLeftButton();
  generator->ReleaseLeftButton();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Move the mouse to the position which is contained by the bounds of the
  // shelf when it is visible should show the auto-hide shelf.
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  const int display_bottom = display.bounds().bottom();
  generator->MoveMouseTo(1, display_bottom - 1);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Mouse press inside the shelf should not hide the AUTO_HIDE_SHOWN shelf.
  generator->MoveMouseTo(
      GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint());
  generator->PressLeftButton();
  generator->ReleaseLeftButton();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Mouse press the system tray should open the system tray bubble.
  generator->MoveMouseTo(
      GetPrimaryUnifiedSystemTray()->GetBoundsInScreen().CenterPoint());
  generator->PressLeftButton();
  generator->ReleaseLeftButton();
  EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());
}

// Tests that tap shelf item in auto-hide shelf should do nothing.
TEST_F(ShelfLayoutManagerTest, TapShelfItemInAutoHideShelf) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  // Create a normal unmaximized window; the shelf should be hidden.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->Show();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Tap home button should not open the app list and shelf should keep
  // hidden.
  gfx::Rect home_button_bounds = shelf->shelf_widget()
                                     ->navigation_widget()
                                     ->GetHomeButton()
                                     ->GetBoundsInScreen();
  gfx::Rect display_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  home_button_bounds.Intersect(display_bounds);
  GetEventGenerator()->GestureTapAt(home_button_bounds.CenterPoint());
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Tests the a11y feedback for entering/exiting fullscreen workspace state.
TEST_F(ShelfLayoutManagerTest, A11yAlertOnWorkspaceState) {
  TestAccessibilityControllerClient client;
  std::unique_ptr<aura::Window> window1(
      AshTestBase::CreateToplevelTestWindow());
  std::unique_ptr<aura::Window> window2(
      AshTestBase::CreateToplevelTestWindow());
  EXPECT_NE(AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_ENTERED,
            client.last_a11y_alert());

  // Toggle the current normal window in workspace to fullscreen should send the
  // ENTERED alert.
  const WMEvent fullscreen(WM_EVENT_TOGGLE_FULLSCREEN);
  WindowState* window_state2 = WindowState::Get(window2.get());
  window_state2->OnWMEvent(&fullscreen);
  EXPECT_TRUE(window_state2->IsFullscreen());
  EXPECT_EQ(AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_ENTERED,
            client.last_a11y_alert());

  // Toggle the current fullscreen'ed window in workspace to exit fullscreen
  // should send the EXITED alert.
  window_state2->OnWMEvent(&fullscreen);
  EXPECT_FALSE(window_state2->IsFullscreen());
  EXPECT_EQ(AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_EXITED,
            client.last_a11y_alert());

  // Fullscreen the |window2| again to prepare for the following tests.
  window_state2->OnWMEvent(&fullscreen);
  EXPECT_TRUE(window_state2->IsFullscreen());
  EXPECT_EQ(AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_ENTERED,
            client.last_a11y_alert());
  // Changes the current window in workspace from a fullscreen window to a
  // normal window should send the EXITD alert.
  window_state2->Minimize();
  EXPECT_EQ(AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_EXITED,
            client.last_a11y_alert());

  // Changes the current window in workspace from a normal window to fullscreen
  // window should send ENTERED alert.
  window_state2->Unminimize();
  EXPECT_TRUE(window_state2->IsFullscreen());
  EXPECT_EQ(AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_ENTERED,
            client.last_a11y_alert());
}

// Verifies the auto-hide shelf is shown if there is only a single PIP window.
TEST_F(ShelfLayoutManagerTest, AutoHideShelfShownForSinglePipWindow) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Create a PIP window.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  // Set always on top so it is put in the PIP container.
  window->SetProperty(aura::client::kZOrderingKey,
                      ui::ZOrderLevel::kFloatingWindow);
  window->Show();
  const WMEvent pip_event(WM_EVENT_PIP);
  WindowState::Get(window)->OnWMEvent(&pip_event);
  Shelf::UpdateShelfVisibility();

  // Expect the shelf to be hidden.
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
}

// Verifies that shelf components are placed properly in right-to-left UI.
TEST_F(ShelfLayoutManagerTest, RtlPlacement) {
  // Helper function to check that the given widget is placed symmetrically
  // between LTR and RTL.
  auto check_mirrored_placement = [](views::Widget* widget) {
    base::i18n::SetICUDefaultLocale("en");
    EXPECT_FALSE(base::i18n::IsRTL());
    GetShelfLayoutManager()->LayoutShelf();
    const int ltr_left_position =
        widget->GetNativeWindow()->GetBoundsInScreen().x();

    base::i18n::SetICUDefaultLocale("ar");
    EXPECT_TRUE(base::i18n::IsRTL());
    GetShelfLayoutManager()->LayoutShelf();
    const int rtl_right_position =
        widget->GetNativeWindow()->GetBoundsInScreen().right();

    EXPECT_EQ(
        GetShelfWidget()->GetWindowBoundsInScreen().width() - ltr_left_position,
        rtl_right_position);
  };

  const std::string locale = base::i18n::GetConfiguredLocale();

  ShelfWidget* shelf_widget = GetPrimaryShelf()->shelf_widget();
  check_mirrored_placement(shelf_widget->navigation_widget());
  check_mirrored_placement(shelf_widget->status_area_widget());
  check_mirrored_placement(shelf_widget);

  // Reset the lauguage setting.
  base::i18n::SetICUDefaultLocale(locale);
}

// Tests the auto-hide shelf status when opening and closing a context menu.
TEST_F(ShelfLayoutManagerTest, AutoHideShelfWithContextMenu) {
  // Create one window, or the shelf won't auto-hide.
  CreateTestWidget();

  // Set the shelf to auto-hide.
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Create an app that we can use to pull up a context menu.
  EXPECT_EQ(0u,
            ShelfViewTestAPI(shelf->GetShelfViewForTesting()).GetButtonCount());
  AddApp();
  EXPECT_EQ(1u,
            ShelfViewTestAPI(shelf->GetShelfViewForTesting()).GetButtonCount());

  // Swipe up to show the shelf.
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Open hotseat context menu.
  ui::test::EventGenerator* generator = GetEventGenerator();
  ShelfAppButton* clickable_app_button =
      ShelfViewTestAPI(shelf->GetShelfViewForTesting()).GetButton(0);
  EXPECT_TRUE(clickable_app_button);
  EXPECT_FALSE(shelf->shelf_widget()->IsShowingMenu());
  generator->MoveMouseTo(
      clickable_app_button->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  generator->ClickRightButton();
  EXPECT_TRUE(shelf->shelf_widget()->IsShowingMenu());

  // Close the context menu with the mouse over the shelf. The shelf should
  // remain shown.
  generator->ClickRightButton();
  ASSERT_FALSE(TriggerAutoHideTimeout());
  EXPECT_FALSE(shelf->shelf_widget()->IsShowingMenu());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Reopen hotseat context menu.
  generator->ClickRightButton();
  EXPECT_TRUE(shelf->shelf_widget()->IsShowingMenu());

  // Mouse away from the shelf with the context menu still showing. The shelf
  // should remain shown.
  generator->MoveMouseTo(0, 0);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Close the context menu with the mouse away from the shelf. The shelf
  // should hide.
  generator->ClickRightButton();
  ASSERT_FALSE(TriggerAutoHideTimeout());
  EXPECT_FALSE(shelf->shelf_widget()->IsShowingMenu());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

namespace {

class DragTestView : public views::View {
 public:
  DragTestView() = default;
  DragTestView(const DragTestView&) = delete;
  DragTestView& operator=(const DragTestView&) = delete;
  ~DragTestView() override = default;

  int DragThreshold() { return views::View::GetVerticalDragThreshold(); }

 private:
  // views::View:
  int GetDragOperations(const gfx::Point& press_pt) override {
    return ui::DragDropTypes::DRAG_COPY;
  }

  void WriteDragData(const gfx::Point& p, OSExchangeData* data) override {
    gfx::ImageSkiaRep image_rep(gfx::Size(1, 1), 1.0f);
    gfx::ImageSkia image_skia(image_rep);
    data->provider().SetDragImage(image_skia, gfx::Vector2d());
  }
};

enum class DragEventType {
  kMouse,
  kGesture,
};

}  // namespace

// Shelf tests parametrized to perform drags using mouse and gesture events.
class ShelfLayoutManagerDragDropTest
    : public ShelfLayoutManagerTestBase,
      public testing::WithParamInterface<DragEventType> {
 public:
  ShelfLayoutManagerDragDropTest() = default;

  void SetUp() override {
    ShelfLayoutManagerTestBase::SetUp();

    auto* drag_drop_controller =
        static_cast<DragDropController*>(aura::client::GetDragDropClient(
            GetPrimaryShelf()->GetWindow()->GetRootWindow()));
    drag_drop_controller->set_should_block_during_drag_drop(false);
    generator_ = GetEventGenerator();
  }

  // Moves to `view` and mouse/gesture presses it. Does not actually start
  // dragging `view` to a different location.
  void StartDrag(DragTestView* view) {
    if (GetParam() == DragEventType::kMouse) {
      generator_->MoveMouseTo(view->GetBoundsInScreen().origin());
      generator_->PressLeftButton();
    } else {
      generator_->PressTouch(view->GetBoundsInScreen().origin());
      ui::GestureEvent long_press(
          generator_->current_screen_location().x(),
          generator_->current_screen_location().y(), ui::EF_NONE,
          ui::EventTimeForNow(),
          ui::GestureEventDetails(ui::EventType::kGestureLongPress));
      generator_->Dispatch(&long_press);
    }
  }

  // Drags a view vertically by `dy` pixels. Assumes the drag has been started.
  void MoveDragBy(int dy) {
    auto move_fn =
        base::BindRepeating(GetParam() == DragEventType::kMouse
                                ? &ui::test::EventGenerator::MoveMouseBy
                                : &ui::test::EventGenerator::MoveTouchBy,
                            base::Unretained(generator_));
    const int step = dy / abs(dy);
    for (int i = 0; i < abs(dy); ++i) {
      move_fn.Run(0, step);
    }
  }

  // Releases the mouse/gesture press that started a drag, possibly triggering
  // a drop.
  void EndDrag() {
    if (GetParam() == DragEventType::kMouse) {
      generator_->ReleaseLeftButton();
    } else {
      generator_->ReleaseTouch();
    }
  }

 private:
  raw_ptr<ui::test::EventGenerator, DanglingUntriaged> generator_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         ShelfLayoutManagerDragDropTest,
                         testing::Values(DragEventType::kMouse,
                                         DragEventType::kGesture));

// Tests the auto-hide shelf status with drag-drop events.
TEST_P(ShelfLayoutManagerDragDropTest, AutoHideShelfOnDragDropEvents) {
  // Create one window, or the shelf won't auto-hide.
  std::unique_ptr<views::Widget> widget = CreateFramelessTestWidget();

  // Set the shelf to auto-hide.
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Create draggable view.
  DragTestView* view =
      widget->SetContentsView(std::make_unique<DragTestView>());
  const int drag_distance =
      view->DragThreshold() + layout_manager->GetIdealBounds().height();
  auto bounds = gfx::Rect(
      0, GetShelfWidget()->GetVisibleShelfBounds().y() - drag_distance, 1, 1);

  // `DragDropController` applies a vertical offset when determining the target
  // view for touch-initiated dragging, so we compensate for that here.
  if (GetParam() == DragEventType::kGesture)
    bounds.Offset(0, /*DragDropController::kTouchDragImageVerticalOffset=*/25);

  widget->SetBounds(bounds);

  // Drag a view to make the shelf appear.
  StartDrag(view);
  MoveDragBy(drag_distance);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Drag a view away to make the shelf disappear.
  MoveDragBy(-drag_distance);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Drag a view to make the shelf reappear (make sure all state has reset).
  MoveDragBy(drag_distance);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // End the drag with mouse over the shelf, so the shelf should stay shown.
  EndDrag();
  ASSERT_FALSE(TriggerAutoHideTimeout());
  EXPECT_EQ(GetParam() == DragEventType::kMouse ? SHELF_AUTO_HIDE_SHOWN
                                                : SHELF_AUTO_HIDE_HIDDEN,
            shelf->GetAutoHideState());

  // Move pointer away to make the shelf disappear.
  StartDrag(view);
  ASSERT_FALSE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Drag a view to make the shelf reappear (make sure all state has reset).
  MoveDragBy(drag_distance);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Verify that dragging does nothing when the shelf is not in auto-hide mode.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  MoveDragBy(-drag_distance);
  MoveDragBy(drag_distance);
  ASSERT_FALSE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  // If the dragging had been observed, the shelf would be shown.
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // TODO(crbug.com/1240332): Test screen exits when behavior is consistent.
}

// Tests that the shelf background does not change when the bubble launcher is
// shown.
TEST_F(ShelfLayoutManagerTest, NoBackgroundChange) {
  const auto shelf_background_type =
      GetShelfLayoutManager()->shelf_background_type();
  AppListControllerImpl* app_list_controller =
      Shell::Get()->app_list_controller();
  // Show the AppListBubble, test that the shelf background has not changed.
  PressHomeButton();
  ASSERT_TRUE(app_list_controller->IsVisible());
  EXPECT_EQ(shelf_background_type,
            GetShelfLayoutManager()->shelf_background_type());

  // Hide the bubble, test that the shelf background has still not changed.
  PressHomeButton();
  ASSERT_FALSE(app_list_controller->IsVisible());
  EXPECT_EQ(shelf_background_type,
            GetShelfLayoutManager()->shelf_background_type());
}

// Tests that tapping the home button is successful on the autohidden shelf.
//
// TODO(crbug.com/40894666): Test is flaky.
TEST_F(ShelfLayoutManagerTest,
       DISABLED_NoTemporaryAutoHideStateWhileOpeningLauncher) {
  // Enable animations and simulate the zero state search called when showing
  // the launcher.
  ui::ScopedAnimationDurationScaleMode duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  GetTestAppListClient()->set_run_zero_state_callback_immediately(false);

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  const display::Display display =
      display::Screen::GetScreen()->GetPrimaryDisplay();

  // Create a window to hide the shelf in auto-hide mode.
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());

  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);

  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);

  {
    AutoHideStateDetector detector;

    // Open the launcher by tapping the home button.
    GestureTapOn(GetPrimaryShelf()->navigation_widget()->GetHomeButton());

    // Wait until the zero state callback is called.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, GetTestAppListClient()->zero_state_search_done_count());

    // No `SHELF_AUTO_HIDE_HIDDEN` should be set when launcher is showing.
    EXPECT_FALSE(detector.WasShelfAutoHidden());
  }

  // The app list should now be visible.
  GetAppListTestHelper()->CheckVisibility(true);
}

class ShelfLayoutManagerWindowDraggingTest : public ShelfLayoutManagerTestBase {
 public:
  ShelfLayoutManagerWindowDraggingTest() = default;
  ~ShelfLayoutManagerWindowDraggingTest() override = default;

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

    TabletModeControllerTestApi().EnterTabletMode();
    base::RunLoop().RunUntilIdle();
  }

  bool IsWindowDragInProgress() {
    return GetShelfLayoutManager()->IsWindowDragInProgress();
  }
};

// Test that when swiping up on the shelf, we may or may not drag up the MRU
// window.
TEST_F(ShelfLayoutManagerWindowDraggingTest, DraggedMRUWindow) {
  const int shelf_widget_height =
      GetShelfWidget()->GetWindowBoundsInScreen().height();
  const int shelf_widget_bottom =
      GetShelfWidget()->GetWindowBoundsInScreen().bottom();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();

  const struct TestCase {
    // The shelf widget whose bounds are used as the base for gesture start and
    // end locations.
    raw_ptr<const views::Widget> widget;
    // Whether the widget bounds are completely in the left part of the split
    // view.
    const bool left_in_split_view;
    // Whether the widget bounds are completely in the right part of the split
    // view.
    const bool right_in_split_view;
    const std::string description;
  } test_cases[] = {
      {GetShelfWidget(), false /*left_in_split_view*/,
       false /*right_in_split_view*/, "Shelf widget"},
      {GetPrimaryShelf()->navigation_widget(), true /*left_in_split_view*/,
       false /*right_in_split_view*/, "Navigation widget"},
      {GetShelfWidget()->status_area_widget(), false /*left_in_split_view*/,
       true /*right_in_split_view*/, "Status area widget"}};

  for (const auto& test_case : test_cases) {
    SCOPED_TRACE(test_case.description);
    ASSERT_TRUE(!test_case.left_in_split_view ||
                !test_case.right_in_split_view);

    // Starts the drag from the center of the shelf's bottom.
    const gfx::Rect widget_bounds = test_case.widget->GetWindowBoundsInScreen();
    // NOTE: Navigation widget might have zero size (depending on whether
    // home and back buttons are shown) - use the shelf widget bottom value to
    // ensure the drag starts from the bottom of the shelf.
    gfx::Point start(widget_bounds.CenterPoint().x(), shelf_widget_bottom);
    StartScroll(start);
    UpdateScroll(
        gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
    // We need at least one window to work with.
    EXPECT_FALSE(IsWindowDragInProgress());
    EndScroll(/*is_fling=*/false, 0.f);

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

    StartScroll(start);
    UpdateScroll(
        gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
    DragWindowFromShelfController* window_drag_controller =
        GetShelfLayoutManager()->window_drag_controller_for_testing();
    EXPECT_TRUE(IsWindowDragInProgress());
    EXPECT_EQ(window_drag_controller->dragged_window(), window.get());
    UpdateScroll(gfx::Vector2d(0, -shelf_widget_height - hotseat_size));
    EXPECT_FALSE(window->transform().IsIdentityOrTranslation());
    EXPECT_TRUE(window->transform().IsScaleOrTranslation());
    EndScroll(/*is_fling=*/false, 0.f);
    EXPECT_FALSE(IsWindowDragInProgress());
    EXPECT_TRUE(window->transform().IsIdentity());

    // The window needs to be visible to drag up.
    window->Hide();
    StartScroll(start);
    UpdateScroll(
        gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
    EXPECT_FALSE(IsWindowDragInProgress());
    EXPECT_TRUE(window->transform().IsIdentity());
    EndScroll(/*is_fling=*/false, 0.f);

    // In splitview, depends on the drag position, the active dragged window
    // might be different.
    window->Show();
    auto window2 = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
    SplitViewController* split_view_controller =
        SplitViewController::Get(Shell::GetPrimaryRootWindow());
    split_view_controller->SnapWindow(window.get(), SnapPosition::kPrimary);
    split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);
    StartScroll(gfx::Point(widget_bounds.x(), shelf_widget_bottom));
    UpdateScroll(
        gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
    window_drag_controller =
        GetShelfLayoutManager()->window_drag_controller_for_testing();
    EXPECT_TRUE(IsWindowDragInProgress());
    aura::Window* drag_window =
        test_case.right_in_split_view ? window2.get() : window.get();
    EXPECT_EQ(window_drag_controller->dragged_window(), drag_window);
    // No window transform at the point where the window drag starts.
    EXPECT_TRUE(drag_window->transform().IsIdentity());
    // The window is expected to be scaled down as the drag progresses.
    UpdateScroll(gfx::Vector2d(0, -10));
    EXPECT_FALSE(drag_window->transform().IsIdentityOrTranslation());
    EXPECT_TRUE(drag_window->transform().IsScaleOrTranslation());
    EndScroll(/*is_fling=*/false, 0.f);
    EXPECT_FALSE(IsWindowDragInProgress());
    EXPECT_TRUE(drag_window->transform().IsIdentity());

    StartScroll(gfx::Point(widget_bounds.right(), shelf_widget_bottom));
    UpdateScroll(
        gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
    window_drag_controller =
        GetShelfLayoutManager()->window_drag_controller_for_testing();
    EXPECT_TRUE(IsWindowDragInProgress());
    drag_window = test_case.left_in_split_view ? window.get() : window2.get();
    EXPECT_EQ(window_drag_controller->dragged_window(), drag_window);
    EXPECT_FALSE(drag_window->transform().IsIdentityOrTranslation());
    EXPECT_TRUE(drag_window->transform().IsScaleOrTranslation());
    EndScroll(/*is_fling=*/false, 0.f);
    split_view_controller->EndSplitView();
    EXPECT_FALSE(IsWindowDragInProgress());
    EXPECT_TRUE(window->transform().IsIdentity());
    EXPECT_TRUE(window2->transform().IsIdentity());
  }
}

// Tests that downward swipe on shelf does not start window drag.
TEST_F(ShelfLayoutManagerWindowDraggingTest,
       DownwardSwipeDoesnotStartWnidowDrag) {
  // Starts the drag from the center of the shelf's bottom.
  const gfx::Rect shelf_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
  gfx::Point start = shelf_bounds.top_center();
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());

  // Tests that downward swipe on shelf does not start window drag, nor change
  // the window transform when hotseat is hidden.
  ASSERT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());
  StartScroll(start);
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  UpdateScroll(gfx::Vector2d(0, shelf_bounds.height() / 2));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  UpdateScroll(gfx::Vector2d(0, shelf_bounds.height() / 2));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  EndScroll(/*is_fling=*/false, 0.f);
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  // Tests that downward swipe on shelf does not start window drag, nor change
  // the window transform when hotseat is extended.
  SwipeUpOnShelf();
  ASSERT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());

  StartScroll(start);
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  UpdateScroll(gfx::Vector2d(0, shelf_bounds.height() / 2));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  UpdateScroll(gfx::Vector2d(0, shelf_bounds.height() / 2));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  EndScroll(/*is_fling=*/false, 0.f);
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
}

// Test that drag from shelf when overview is active is a no-op.
TEST_F(ShelfLayoutManagerWindowDraggingTest, NoOpInOverview) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  std::unique_ptr<aura::Window> window2 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window1.get());

  // Starts the drag from the center of the shelf's bottom.
  EnterOverview();
  gfx::Point start = shelf_widget_bounds.bottom_center();
  StartScroll(start);
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EXPECT_FALSE(IsWindowDragInProgress());
  EndScroll(/*is_fling=*/false, 0.f);

  // In splitview + overview case, drag from shelf in the overview side of the
  // screen also does nothing.
  SplitViewController* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);
  EnterOverview();
  EXPECT_TRUE(split_view_controller->InSplitViewMode());
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  StartScroll(shelf_widget_bounds.bottom_right());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EXPECT_FALSE(IsWindowDragInProgress());
  EndScroll(/*is_fling=*/false, 0.f);
}

// Test that upward fling to exit overview mode does not cause the shelf to
// animate if we are in kShownHomeLauncher.
TEST_F(ShelfLayoutManagerWindowDraggingTest, SwipeToExitOverview) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  // Hide |window1| so we remain in kShownHomeLauncher when we enter overview.
  window1->Hide();
  EXPECT_EQ(HotseatState::kShownHomeLauncher, GetHotseatWidget()->state());

  EnterOverview();
  GetHotseatWidget()->SetState(HotseatState::kShownHomeLauncher);
  const gfx::Rect hotseat_bounds = GetHotseatWidget()->GetTargetBounds();

  // Fling up from the center of the shelf's bottom.
  StartScroll(shelf_widget_bounds.bottom_center());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  // Hotseat should move as it is in the |kShownHomeLauncher| state.
  EXPECT_EQ(hotseat_bounds, GetHotseatWidget()->GetTargetBounds());
  EndScroll(
      true /* is_fling */,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  // We should exit overview mode after completing the fling gesture.
  EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
}

// Test that upward fling in overview transitions from overview to home.
TEST_F(ShelfLayoutManagerWindowDraggingTest, FlingInOverview) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window1.get());

  EnterOverview();

  base::HistogramTester histogram_tester;
  HotseatStateWatcher watcher(GetShelfLayoutManager());

  SwipeUpOnShelf();
  watcher.CheckEqual({HotseatState::kExtended});

  // Fling up from the center of the shelf's bottom.
  StartScroll(shelf_widget_bounds.bottom_center());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EndScroll(
      /*is_fling=*/true,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  EXPECT_FALSE(OverviewController::Get()->InOverviewSession());

  watcher.WaitUntilStateChanged();
  watcher.CheckEqual(
      {HotseatState::kExtended, HotseatState::kShownHomeLauncher});

  histogram_tester.ExpectBucketCount(
      kHotseatGestureHistogramName,
      InAppShelfGestures::kFlingUpToShowHomeScreen, 1);
  histogram_tester.ExpectBucketCount(kHotseatGestureHistogramName,
                                     InAppShelfGestures::kSwipeUpToShow, 1);
}

// Test that upward fling in overview transitions from home shelf overview to
// home.
TEST_F(ShelfLayoutManagerWindowDraggingTest, FlingInOverviewHomeShelf) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  // This will ensure we enter overview in home shelf mode.
  WindowState::Get(window1.get())->Minimize();

  OverviewController* overview_controller = OverviewController::Get();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  base::HistogramTester histogram_tester;

  // Fling up from the center of the shelf's bottom.
  StartScroll(shelf_widget_bounds.bottom_center());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EndScroll(
      true /* is_fling */,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  // Exit overview session.
  EXPECT_FALSE(overview_controller->InOverviewSession());

  histogram_tester.ExpectBucketCount(
      kHotseatGestureHistogramName,
      InAppShelfGestures::kFlingUpToShowHomeScreen, 1);
  histogram_tester.ExpectBucketCount(kHotseatGestureHistogramName,
                                     InAppShelfGestures::kSwipeUpToShow, 0);
}

// Test that upward fling in split mode on overview side shows hotseat and
// remains in split view is the swipe is short.
TEST_F(ShelfLayoutManagerWindowDraggingTest,
       FlingToShowHotseatInSplitModeWithOverview) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 = CreateAppWindow(gfx::Rect(400, 400));
  std::unique_ptr<aura::Window> window2 = CreateAppWindow(gfx::Rect(400, 400));

  SplitViewController* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);
  OverviewController* overview_controller = OverviewController::Get();

  base::HistogramTester histogram_tester;
  HotseatStateWatcher watcher(GetShelfLayoutManager());

  // Short fling (little longer than the drag required to show the extended
  // hotseat).
  StartScroll(shelf_widget_bounds.bottom_right());
  UpdateScroll(gfx::Vector2d(
      0, -shelf_size - 1.5f * hotseat_size - hotseat_padding_size));
  EndScroll(
      /*is_fling=*/true,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  EXPECT_FALSE(overview_controller->InOverviewSession());
  EXPECT_TRUE(split_view_controller->InSplitViewMode());
  watcher.CheckEqual({HotseatState::kExtended});

  histogram_tester.ExpectBucketCount(kHotseatGestureHistogramName,
                                     InAppShelfGestures::kSwipeUpToShow, 1);

  // The same fling gesture should transition to overview since the hotseat is
  // in extended state.
  StartScroll(shelf_widget_bounds.bottom_right());
  UpdateScroll(gfx::Vector2d(
      0, -shelf_size - 1.5f * hotseat_size - hotseat_padding_size));
  EndScroll(
      /*is_fling=*/true,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  EXPECT_TRUE(overview_controller->InOverviewSession());
  EXPECT_TRUE(split_view_controller->InSplitViewMode());

  watcher.CheckEqual({HotseatState::kExtended, HotseatState::kHidden});

  // Swipe up on the shelf to show the hotseat.
  SwipeUpOnShelf();

  // The same fling gesture should transition to home since overview mode
  // is active.
  StartScroll(shelf_widget_bounds.bottom_right());
  UpdateScroll(gfx::Vector2d(
      0, -shelf_size - 1.5f * hotseat_size - hotseat_padding_size));
  EndScroll(
      /*is_fling=*/true,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  EXPECT_FALSE(overview_controller->InOverviewSession());
  EXPECT_FALSE(split_view_controller->InSplitViewMode());

  watcher.WaitUntilStateChanged();
  watcher.CheckEqual({HotseatState::kExtended, HotseatState::kHidden,
                      HotseatState::kExtended,
                      HotseatState::kShownHomeLauncher});

  histogram_tester.ExpectBucketCount(
      kHotseatGestureHistogramName,
      InAppShelfGestures::kFlingUpToShowHomeScreen, 1);
  histogram_tester.ExpectBucketCount(kHotseatGestureHistogramName,
                                     InAppShelfGestures::kSwipeUpToShow, 2);
}

// Test that upward fling in split view on overview side transitions to home, if
// the swipe length is long enough.
TEST_F(ShelfLayoutManagerWindowDraggingTest, FlingHomeInSplitViewWithOverview) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 = CreateAppWindow(gfx::Rect(400, 400));
  std::unique_ptr<aura::Window> window2 = CreateAppWindow(gfx::Rect(400, 400));

  SplitViewController* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);
  OverviewController* overview_controller = OverviewController::Get();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());

  base::HistogramTester histogram_tester;
  HotseatStateWatcher watcher(GetShelfLayoutManager());

  // Longer fling, one that significantly exceeds the distance required to show
  // the hotseat (by 2 hotseat heights).
  StartScroll(shelf_widget_bounds.bottom_right());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - 3 * hotseat_size - hotseat_padding_size));
  EndScroll(
      /*is_fling=*/true,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  EXPECT_FALSE(overview_controller->InOverviewSession());
  EXPECT_FALSE(split_view_controller->InSplitViewMode());

  watcher.WaitUntilStateChanged();
  EXPECT_EQ(HotseatState::kShownHomeLauncher,
            GetShelfLayoutManager()->hotseat_state());

  histogram_tester.ExpectBucketCount(
      kHotseatGestureHistogramName,
      InAppShelfGestures::kFlingUpToShowHomeScreen, 1);
  histogram_tester.ExpectBucketCount(kHotseatGestureHistogramName,
                                     InAppShelfGestures::kSwipeUpToShow, 0);
}

// Tests that the hotseat ends up in manually extended state after swiping up
// a window in split screen to overview (the final state is a split screen with
// one side in overview).
TEST_F(ShelfLayoutManagerWindowDraggingTest, FlingInSplitView) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 = CreateAppWindow(gfx::Rect(400, 400));
  std::unique_ptr<aura::Window> window2 = CreateAppWindow(gfx::Rect(400, 400));

  SplitViewController* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);

  base::HistogramTester histogram_tester;
  HotseatStateWatcher watcher(GetShelfLayoutManager());

  // Longer fling, one that significantly exceeds the distance required to show
  // the hotseat (by 2 hotseat heights).
  StartScroll(shelf_widget_bounds.bottom_left());
  // Ensure swipe goes past the top of the hotseat first to activate the window
  // drag controller
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size - 10));
  UpdateScroll(gfx::Vector2d(0, -2 * hotseat_size));
  EndScroll(
      /*is_fling=*/true,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  EXPECT_TRUE(split_view_controller->InSplitViewMode());

  watcher.CheckEqual({});

  histogram_tester.ExpectBucketCount(
      kHotseatGestureHistogramName,
      InAppShelfGestures::kFlingUpToShowHomeScreen, 0);
  histogram_tester.ExpectBucketCount(kHotseatGestureHistogramName,
                                     InAppShelfGestures::kSwipeUpToShow, 1);
}

// Tests that the hotseat ends up in manually extended state after swiping up
// hotseat when window drag from shelf in split view ends up restoring original
// window bounds.
TEST_F(ShelfLayoutManagerWindowDraggingTest, ShortFlingInSplitView) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  std::unique_ptr<aura::Window> window1 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  std::unique_ptr<aura::Window> window2 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));

  SplitViewController* split_view_controller =
      SplitViewController::Get(Shell::GetPrimaryRootWindow());
  split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);

  base::HistogramTester histogram_tester;
  HotseatStateWatcher watcher(GetShelfLayoutManager());

  StartScroll(shelf_widget_bounds.bottom_left());
  UpdateScroll(gfx::Vector2d(
      0, -shelf_size - 1.5f * hotseat_size - hotseat_padding_size));
  EndScroll(
      true /* is_fling */,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
  EXPECT_TRUE(split_view_controller->InSplitViewMode());

  watcher.CheckEqual({HotseatState::kExtended});
  EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->is_manually_extended());

  histogram_tester.ExpectBucketCount(
      kHotseatGestureHistogramName,
      InAppShelfGestures::kFlingUpToShowHomeScreen, 0);
  histogram_tester.ExpectBucketCount(kHotseatGestureHistogramName,
                                     InAppShelfGestures::kSwipeUpToShow, 1);
}

// Tests that hotseat transition animation is not delayed (i.e. that it happens
// as soon as shelf opaque background changes) when virtual keyboard is hidden,
// and the user swipes from shelf to home.
TEST_F(ShelfLayoutManagerWindowDraggingTest,
       NoDelayedAnimatingBackgroundForTransitionFromVirtualKeyboardToHome) {
  std::unique_ptr<aura::Window> window1 =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window1.get());

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

  // Show virtual keyboard.
  KeyboardController* const keyboard_controller =
      Shell::Get()->keyboard_controller();
  keyboard_controller->SetEnableFlag(
      keyboard::KeyboardEnableFlag::kShelfEnabled);
  keyboard_controller->ShowKeyboard();

  // Verify the shelf state.
  EXPECT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
  EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
  ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
  ASSERT_FALSE(GetShelfWidget()
                   ->GetAnimatingBackground()
                   ->GetAnimator()
                   ->is_animating());
  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());

  // Make animations not end immediately for the rest of the test (so the test
  // can test whether the animating shelf background is animating).
  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();

  // Simulate virtual keyboard closing, and a swipe from shelf to home.
  StartScroll(shelf_widget_bounds.bottom_right());
  keyboard_controller->HideKeyboard(HideReason::kUser);

  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - 3 * hotseat_size - hotseat_padding_size));
  EndScroll(
      true /* is_fling */,
      -(DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));

  // Verify that the shelf background start animating immediately.
  EXPECT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
  EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
  ASSERT_TRUE(GetShelfWidget()->GetAnimatingBackground()->visible());
  ASSERT_TRUE(GetShelfWidget()
                  ->GetAnimatingBackground()
                  ->GetAnimator()
                  ->is_animating());

  keyboard_controller->ClearEnableFlag(
      keyboard::KeyboardEnableFlag::kShelfEnabled);
}

// Test that if shelf if hidden or auto-hide hidden, drag window from shelf is a
// no-op.
// TODO(crbug.com/40107332): This test consistently crashes.
TEST_F(ShelfLayoutManagerWindowDraggingTest, DISABLED_NoOpForHiddenShelf) {
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());
  Shelf* shelf = GetPrimaryShelf();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();

  // The window can be dragged on a visible shelf.
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  StartScroll(shelf_widget_bounds.bottom_center());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EXPECT_TRUE(IsWindowDragInProgress());
  EXPECT_FALSE(window->transform().IsIdentityOrTranslation());
  EndScroll(/*is_fling=*/false, 0.f);

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // The window can't be dragged on an auto-hidden hidden shelf.
  gfx::Rect display_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  StartScroll(display_bounds.bottom_center());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  EndScroll(/*is_fling=*/false, 0.f);

  // The window can be dragged on an auto-hidden shown shelf.
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  StartScroll(display_bounds.bottom_center());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EXPECT_TRUE(IsWindowDragInProgress());
  EXPECT_FALSE(window->transform().IsIdentityOrTranslation());
  EXPECT_TRUE(window->transform().IsScaleOrTranslation());
  EndScroll(/*is_fling=*/false, 0.f);

  // The window can't be dragged on a hidden shelf.
  SetState(GetShelfLayoutManager(), SHELF_HIDDEN);
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
  StartScroll(display_bounds.bottom_center());
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  EndScroll(/*is_fling=*/false, 0.f);
}

// Tests that dragging below the hotseat after dragging the MRU up results in
// the hotseat not moving from its extended position.
TEST_F(ShelfLayoutManagerWindowDraggingTest,
       DragBelowHotseatDoesNotMoveHotseat) {
  // Go to in-app shelf, then drag the hotseat up until it is extended, this
  // will start a window drag.
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  gfx::Point start = shelf_widget_bounds.bottom_center();
  StartScroll(start);
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  const int hotseat_y =
      GetPrimaryShelf()->hotseat_widget()->GetWindowBoundsInScreen().y();

  // Drag down, the hotseat should not move because it was extended when the
  // window drag began.
  UpdateScroll(gfx::Vector2d(0, 10));

  EXPECT_EQ(hotseat_y,
            GetPrimaryShelf()->hotseat_widget()->GetWindowBoundsInScreen().y());
  EndScroll(/*is_fling=*/false, 0.f);
}

// Tests that dragging below the hotseat after dragging the MRU up results in
// the hotseat not moving from its extended position with an autohidden shelf.
TEST_F(ShelfLayoutManagerWindowDraggingTest,
       DragBelowHotseatDoesNotMoveHotseatAutoHiddenShelf) {
  // Extend the hotseat, then start dragging the window.
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());
  SwipeUpOnShelf();
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const gfx::Point start = shelf_widget_bounds.bottom_center();
  StartScroll(start);
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  UpdateScroll(
      gfx::Vector2d(0, -shelf_size - hotseat_size - hotseat_padding_size));
  const int hotseat_y =
      GetPrimaryShelf()->hotseat_widget()->GetWindowBoundsInScreen().y();

  // Drag down, the hotseat should not move because it was extended when the
  // window drag began.
  UpdateScroll(gfx::Vector2d(0, 10));

  EXPECT_EQ(hotseat_y,
            GetPrimaryShelf()->hotseat_widget()->GetWindowBoundsInScreen().y());
  EndScroll(/*is_fling=*/false, 0.f);
}

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

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());

  gfx::Rect hotseat_bounds =
      GetPrimaryShelf()->hotseat_widget()->GetWindowBoundsInScreen();
  StartScroll(hotseat_bounds.CenterPoint());
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  EndScroll(/*is_fling=*/false, 0.f);

  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());
}

// Test that gesture that starts within hotseat bounds, goes down to shelf, and
// start moving up does not start window drag (as upward swipe from hotseat does
// not start window drag either).
TEST_F(ShelfLayoutManagerWindowDraggingTest,
       NoOpIfDragSTartsAboveShelfAndMovesToShelf) {
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());

  gfx::Rect hotseat_bounds =
      GetPrimaryShelf()->hotseat_widget()->GetWindowBoundsInScreen();
  StartScroll(hotseat_bounds.CenterPoint());
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  const gfx::Vector2d vector_from_hotseat_to_shelf_center =
      hotseat_bounds.CenterPoint() -
      GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
  UpdateScroll(gfx::Vector2d(0, vector_from_hotseat_to_shelf_center.y()));

  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  UpdateScroll(gfx::Vector2d(0, -vector_from_hotseat_to_shelf_center.y()));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());

  EndScroll(/*is_fling=*/false, 0.f);
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
}

// Tests that the MRU window can only be dragged window after the hotseat is
// fully dragged up if hotseat was hidden before.
TEST_F(ShelfLayoutManagerWindowDraggingTest, StartsDragAfterHotseatIsUp) {
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());

  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  const int shelf_size = ShelfConfig::Get()->shelf_size();
  const int hotseat_size = GetHotseatWidget()->GetHotseatSize();
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();

  // Starts the drag from the center of the shelf's bottom.
  gfx::Point start = shelf_widget_bounds.bottom_center();
  StartScroll(start);
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  // Continues the drag until the hotseat should have been fully dragged up.
  UpdateScroll(gfx::Vector2d(0, -shelf_size));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  UpdateScroll(gfx::Vector2d(0, -hotseat_padding_size));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  UpdateScroll(gfx::Vector2d(0, -hotseat_size));
  EXPECT_TRUE(IsWindowDragInProgress());
  // The window should not be transformed at the point where the drag starts.
  EXPECT_TRUE(window->transform().IsIdentity());
  // The window is expected to be scaled down as the drag progresses.
  UpdateScroll(gfx::Vector2d(0, -10));
  EXPECT_FALSE(window->transform().IsIdentityOrTranslation());
  EXPECT_TRUE(window->transform().IsScaleOrTranslation());
  EndScroll(/*is_fling=*/false, 0.f);
}

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

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  SwipeUpOnShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());

  // Start drag on the extended hotseat.
  const int hotseat_padding_size = ShelfConfig::Get()->hotseat_bottom_padding();
  gfx::Rect hotseat_bounds =
      GetPrimaryShelf()->hotseat_widget()->GetWindowBoundsInScreen();
  StartScroll(hotseat_bounds.CenterPoint());
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  UpdateScroll(
      gfx::Vector2d(0, hotseat_bounds.height() + hotseat_padding_size));
  EXPECT_FALSE(IsWindowDragInProgress());
  EXPECT_TRUE(window->transform().IsIdentity());
  EndScroll(/*is_fling=*/false, 0.f);
}

// Verifies that there is no crash on shutdown while swipe from home to overview
// is in progress.
TEST_F(ShelfLayoutManagerWindowDraggingTest,
       ShutdownWhileSwipingHomeToOverview) {
  const gfx::Rect shelf_widget_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();
  StartScroll(shelf_widget_bounds.bottom_center());
  UpdateScroll(gfx::Vector2d(0, -20));
  UpdateScroll(gfx::Vector2d(0, -20));
  ASSERT_TRUE(
      GetShelfLayoutManager()->swipe_home_to_overview_controller_for_testing());
}

class ShelfLayoutManagerKeyboardTest : public AshTestBase {
 public:
  ShelfLayoutManagerKeyboardTest() = default;

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

  ~ShelfLayoutManagerKeyboardTest() override = default;

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();
    UpdateDisplay("800x600");
    keyboard::SetTouchKeyboardEnabled(true);
    keyboard::SetAccessibilityKeyboardEnabled(true);
  }

  // AshTestBase:
  void TearDown() override {
    keyboard::SetAccessibilityKeyboardEnabled(false);
    keyboard::SetTouchKeyboardEnabled(false);
    AshTestBase::TearDown();
  }

  void InitKeyboardBounds() {
    gfx::Rect work_area(
        display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
    keyboard_bounds_.SetRect(work_area.x(),
                             work_area.y() + work_area.height() / 2,
                             work_area.width(), work_area.height() / 2);
  }

  void NotifyKeyboardChanging(ShelfLayoutManager* layout_manager,
                              bool is_locked,
                              const gfx::Rect& bounds_in_screen) {
    WorkAreaInsets* work_area_insets = GetPrimaryWorkAreaInsets();
    KeyboardStateDescriptor state;
    state.visual_bounds = bounds_in_screen;
    state.occluded_bounds_in_screen = bounds_in_screen;
    state.displaced_bounds_in_screen =
        is_locked ? bounds_in_screen : gfx::Rect();
    state.is_visible = !bounds_in_screen.IsEmpty();
    work_area_insets->OnKeyboardVisibilityChanged(state.is_visible);
    work_area_insets->OnKeyboardAppearanceChanged(state);
  }

  const gfx::Rect& keyboard_bounds() const { return keyboard_bounds_; }

 private:
  gfx::Rect keyboard_bounds_;
};

TEST_F(ShelfLayoutManagerKeyboardTest, ShelfNotMoveOnKeyboardOpen) {
  gfx::Rect orig_bounds = GetShelfWidget()->GetWindowBoundsInScreen();

  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  InitKeyboardBounds();
  auto* kb_controller = keyboard::KeyboardUIController::Get();
  // Open keyboard in non-sticky mode.
  kb_controller->ShowKeyboard(false);
  NotifyKeyboardChanging(layout_manager, false, keyboard_bounds());

  // Shelf position should not be changed.
  EXPECT_EQ(orig_bounds, GetShelfWidget()->GetWindowBoundsInScreen());
}

// When kAshUseNewVKWindowBehavior flag enabled, do not change accessibility
// keyboard work area in non-sticky mode.
TEST_F(ShelfLayoutManagerKeyboardTest,
       ShelfIgnoreWorkAreaChangeInNonStickyMode) {
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  InitKeyboardBounds();
  auto* kb_controller = keyboard::KeyboardUIController::Get();
  gfx::Rect orig_work_area(
      display::Screen::GetScreen()->GetPrimaryDisplay().work_area());

  // Open keyboard in non-sticky mode.
  kb_controller->ShowKeyboard(false);
  NotifyKeyboardChanging(layout_manager, false, keyboard_bounds());

  // Work area should not be changed.
  EXPECT_EQ(orig_work_area,
            display::Screen::GetScreen()->GetPrimaryDisplay().work_area());

  kb_controller->HideKeyboardExplicitlyBySystem();
  NotifyKeyboardChanging(layout_manager, false, gfx::Rect());
  EXPECT_EQ(orig_work_area,
            display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
}

// Change accessibility keyboard work area in sticky mode.
TEST_F(ShelfLayoutManagerKeyboardTest, ShelfShouldChangeWorkAreaInStickyMode) {
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  InitKeyboardBounds();
  auto* kb_controller = keyboard::KeyboardUIController::Get();
  gfx::Rect orig_work_area(
      display::Screen::GetScreen()->GetPrimaryDisplay().work_area());

  // Open keyboard in sticky mode.
  kb_controller->ShowKeyboard(true);
  NotifyKeyboardChanging(layout_manager, true, keyboard_bounds());

  // Work area should be changed.
  EXPECT_NE(orig_work_area,
            display::Screen::GetScreen()->GetPrimaryDisplay().work_area());

  // Hide the keyboard.
  kb_controller->HideKeyboardByUser();
  NotifyKeyboardChanging(layout_manager, true, gfx::Rect());

  // Work area should be reset to its original value.
  EXPECT_EQ(orig_work_area,
            display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
}

// Make sure we don't update the work area during overview animation
// (crbug.com/947343).
TEST_F(ShelfLayoutManagerTest, NoShelfUpdateDuringOverviewAnimation) {
  // Finish lid detection task.
  base::RunLoop().RunUntilIdle();
  TabletModeControllerTestApi().EnterTabletMode();
  // Run overview animations.
  ui::ScopedAnimationDurationScaleMode regular_animations(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  std::unique_ptr<aura::Window> fullscreen(CreateTestWindow());
  fullscreen->SetProperty(aura::client::kShowStateKey,
                          ui::SHOW_STATE_FULLSCREEN);
  wm::ActivateWindow(fullscreen.get());

  TestDisplayObserver observer;
  EnterOverview();
  WaitForOverviewAnimation(/*enter=*/true);
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  EXPECT_EQ(0, observer.metrics_change_count());
  ExitOverview();
  WaitForOverviewAnimation(/*enter=*/false);
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  EXPECT_EQ(0, observer.metrics_change_count());
}

// Tests that shelf bounds are updated properly after overview animation.
TEST_F(ShelfLayoutManagerTest, ShelfBoundsUpdateAfterOverviewAnimation) {
  // Run overview animations.
  ui::ScopedAnimationDurationScaleMode regular_animations(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  Shelf* shelf = GetPrimaryShelf();
  ASSERT_EQ(ShelfAlignment::kBottom, shelf->alignment());
  const gfx::Rect bottom_shelf_bounds =
      GetShelfWidget()->GetWindowBoundsInScreen();

  const int shelf_size = bottom_shelf_bounds.height();
  const gfx::Rect display_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  const gfx::Rect left_shelf_bounds =
      gfx::Rect(display_bounds.x(), display_bounds.y(), shelf_size,
                display_bounds.height());

  // Change alignment during overview enter animation.
  EnterOverview();
  // When setting the shelf alignment, bounds aren't expected to animate.
  shelf->SetAlignment(ShelfAlignment::kLeft);
  // Setting alignment exits overview which we should wait for.
  WaitForOverviewAnimation(/*enter=*/false);
  EXPECT_EQ(left_shelf_bounds, GetShelfWidget()->GetWindowBoundsInScreen());

  // Change alignment during overview exit animation.
  EnterOverview();
  WaitForOverviewAnimation(/*enter=*/true);
  ExitOverview();
  // When setting the shelf alignment, bounds aren't expected to animate.
  shelf->SetAlignment(ShelfAlignment::kBottom);
  WaitForOverviewAnimation(/*enter=*/false);
  EXPECT_EQ(bottom_shelf_bounds, GetShelfWidget()->GetWindowBoundsInScreen());
}

// Tests that the shelf on a second display is properly centered.
TEST_F(ShelfLayoutManagerTest, ShelfRemainsCenteredOnSecondDisplay) {
  // Create two displays.
  UpdateDisplay("600x400,1000x700");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  EXPECT_EQ(2U, root_windows.size());

  Shelf* shelf_1 = Shelf::ForWindow(root_windows[0]);
  Shelf* shelf_2 = Shelf::ForWindow(root_windows[1]);
  EXPECT_NE(shelf_1, shelf_2);
  EXPECT_NE(shelf_1->GetWindow()->GetRootWindow(),
            shelf_2->GetWindow()->GetRootWindow());

  ShelfView* shelf_view_1 = shelf_1->GetShelfViewForTesting();
  ShelfView* shelf_view_2 = shelf_2->GetShelfViewForTesting();

  const display::Display display_1 =
      display::Screen::GetScreen()->GetPrimaryDisplay();
  const display::Display display_2 =
      display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]);
  EXPECT_NE(display_1, display_2);

  ShelfTestUtil::AddAppShortcut("app_id", TYPE_PINNED_APP);
  ShelfViewTestAPI(GetPrimaryShelf()->GetShelfViewForTesting())
      .RunMessageLoopUntilAnimationsDone();
  gfx::Point app_center_1 = shelf_1->GetShelfViewForTesting()
                                ->first_visible_button_for_testing()
                                ->bounds()
                                .CenterPoint();
  views::View::ConvertPointToScreen(shelf_view_1, &app_center_1);

  gfx::Point app_center_2 = shelf_2->GetShelfViewForTesting()
                                ->first_visible_button_for_testing()
                                ->bounds()
                                .CenterPoint();
  views::View::ConvertPointToScreen(shelf_view_2, &app_center_2);

  // The app icon should be at the horizontal center of each display.
  EXPECT_EQ(display_1.bounds().CenterPoint().x(), app_center_1.x());
  EXPECT_EQ(display_2.bounds().CenterPoint().x(), app_center_2.x());
}

// Verifies that showing the system tray view on the secondary display
// should not affect the auto-hide shelf on the primary display
// (https://crbug.com/1079464).
TEST_F(ShelfLayoutManagerTest, VerifyAutoHideBehaviorOnMultipleDisplays) {
  UpdateDisplay("800x600, 800x600");
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  CreateTestWidget();

  // The primary shelf should be hidden.
  ASSERT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Set focus on the secondary display.
  aura::Window* secondary_root_window =
      Shell::GetRootWindowForDisplayId(GetSecondaryDisplay().id());
  Shell::SetRootWindowForNewWindows(secondary_root_window);

  // Show the system tray on the secondary display.
  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
      AcceleratorAction::kToggleSystemTrayBubble, {});
  Shelf* secondary_shelf =
      RootWindowController::ForWindow(secondary_root_window)->shelf();
  ASSERT_TRUE(secondary_shelf->status_area_widget()->IsMessageBubbleShown());
  ASSERT_FALSE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());

  // Verify that the primary shelf is still hidden.
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

// Tests that pinned app icons are visible on non-primary displays.
TEST_F(ShelfLayoutManagerTest, ShelfShowsPinnedAppsOnOtherDisplays) {
  // Create three displays. Should use 600+ pixel as the horizontal display
  // size, otherwise there's no enough space to show both the date tray and
  // unified system tray on the screen.
  UpdateDisplay("700x400,1000x700,800x900");
  const unsigned int display_count = 3U;
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  EXPECT_EQ(display_count, root_windows.size());

  // Keep this low so that all apps fit at the center of the screen on all
  // displays.
  const size_t max_app_count = 4;
  for (size_t app_count = 1; app_count <= max_app_count; ++app_count) {
    AddApp();

    // Wait for everything to settle down.
    for (unsigned int display_index = 0; display_index < display_count;
         ++display_index) {
      Shelf* shelf = Shelf::ForWindow(root_windows[display_index]);
      ShelfView* shelf_view = shelf->GetShelfViewForTesting();
      ShelfViewTestAPI(shelf_view).RunMessageLoopUntilAnimationsDone();
    }

    // If everything is as expected, the middle app (if applicable) should be
    // exactly at the center of the screen, on all displays. Also, the
    // distance between the first app and the left edge of the display should be
    // the same as the distance between the third app and the right edge of the
    // display.
    for (unsigned int display_index = 0; display_index < display_count;
         ++display_index) {
      const display::Display display =
          display::Screen::GetScreen()->GetDisplayNearestWindow(
              root_windows[display_index]);
      Shelf* shelf = Shelf::ForWindow(root_windows[display_index]);
      ShelfView* shelf_view = shelf->GetShelfViewForTesting();

      EXPECT_EQ(app_count, shelf_view->number_of_visible_apps());

      // Only check the middle app if we have an odd number of apps.
      if (app_count % 2 == 1) {
        const gfx::Point center = ShelfViewTestAPI(shelf_view)
                                      .GetViewAt(app_count / 2)
                                      ->GetBoundsInScreen()
                                      .CenterPoint();
        EXPECT_EQ(display.bounds().CenterPoint().x(), center.x())
            << "App at index " << (app_count / 2) << " should be at "
            << "the center of display " << display_index << " with "
            << app_count << " apps";
      }

      const gfx::Point left = ShelfViewTestAPI(shelf_view)
                                  .GetViewAt(0)
                                  ->GetBoundsInScreen()
                                  .left_center();
      const gfx::Point right = ShelfViewTestAPI(shelf_view)
                                   .GetViewAt(app_count - 1)
                                   ->GetBoundsInScreen()
                                   .right_center();

      EXPECT_EQ(left.x() - display.bounds().x(),
                display.bounds().right() - right.x())
          << "Apps on either end should be at the same distance from the "
          << "screen edge on display " << display_index << " with " << app_count
          << " apps";
    }
  }
}

class QuickActionShowBubbleTest : public ShelfLayoutManagerTestBase,
                                  public testing::WithParamInterface<bool> {
 public:
  QuickActionShowBubbleTest() : scoped_locale_(GetParam() ? "ar" : "") {}
  ~QuickActionShowBubbleTest() override = default;

 private:
  base::test::ScopedRestoreICUDefaultLocale scoped_locale_;
};

const struct {
  ShelfAlignment alignment;
  bool swipe_gesture;
} test_table[]{
    {ShelfAlignment::kBottom, false},
    {ShelfAlignment::kBottom, true},
    {ShelfAlignment::kBottomLocked, false},
    {ShelfAlignment::kBottomLocked, true},
    {ShelfAlignment::kLeft, false},
    {ShelfAlignment::kLeft, true},
    {ShelfAlignment::kRight, false},
    {ShelfAlignment::kRight, true},
};

// Used to test RTL UI orientation.
INSTANTIATE_TEST_SUITE_P(All, QuickActionShowBubbleTest, testing::Bool());

// Tests that the two finger gesture and the swipe gesture when the mouse is
// over the shelf near the edge shows the bubble launcher.
TEST_P(QuickActionShowBubbleTest, ScrollFromShelfToShowAppList) {
  base::HistogramTester histogram_tester;
  const int scroll_offset_threshold =
      ShelfConfig::Get()->mousewheel_scroll_offset_threshold() + 10;
  int bucket_scroll_count = 0;
  int bucket_swipe_count = 0;

  for (auto test : test_table) {
    GetShelfLayoutManager()->LayoutShelf();
    GetPrimaryShelf()->SetAlignment(test.alignment);
    ASSERT_EQ(test.alignment, GetPrimaryShelf()->alignment());

    // Direction of the swipe gesture depends on the shelf alignment and on the
    // event being a swipe or a fling.
    gfx::Vector2d offset = GetPrimaryShelf()->SelectValueForShelfAlignment(
        gfx::Vector2d(0, scroll_offset_threshold),
        gfx::Vector2d(-scroll_offset_threshold, 0),
        gfx::Vector2d(scroll_offset_threshold, 0));

    // Action performed from the navigation_widget should show the bubble
    // launcher.
    const gfx::Point navigation_widget_center = GetPrimaryShelf()
                                                    ->navigation_widget()
                                                    ->GetContentsView()
                                                    ->GetBoundsInScreen()
                                                    .CenterPoint();
    if (test.swipe_gesture) {
      FlingBetweenLocations(navigation_widget_center,
                            navigation_widget_center - offset);
      ++bucket_swipe_count;
    } else {
      DoTwoFingerScrollAtLocation(navigation_widget_center, offset.x(),
                                  offset.y(), false);
      ++bucket_scroll_count;
    }

    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckVisibility(true);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kSwipeFromShelf,
                                       bucket_swipe_count);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kScrollFromShelf,
                                       bucket_scroll_count);

    // The same gesture on the opposite direction should dismiss the app list.
    if (test.swipe_gesture) {
      FlingBetweenLocations(navigation_widget_center,
                            navigation_widget_center + offset);
    } else {
      DoTwoFingerScrollAtLocation(navigation_widget_center, -offset.x(),
                                  -offset.y(), false);
    }
    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckVisibility(false);

    // Action performed from the status area should not show the bubble
    // launcher.
    const gfx::Point status_area_widget_center = GetShelfWidget()
                                                     ->status_area_widget()
                                                     ->GetContentsView()
                                                     ->GetBoundsInScreen()
                                                     .CenterPoint();
    if (test.swipe_gesture) {
      FlingBetweenLocations(status_area_widget_center,
                            status_area_widget_center - offset);
    } else {
      DoTwoFingerScrollAtLocation(status_area_widget_center, offset.x(),
                                  offset.y(), false);
    }

    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckVisibility(false);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kSwipeFromShelf,
                                       bucket_swipe_count);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kScrollFromShelf,
                                       bucket_scroll_count);
  }
}

// Tests that the two finger gesture and the swipe gesture when the mouse is
// over the shelf before the shelf apps, does not show the bubble launcher.
TEST_P(QuickActionShowBubbleTest, ScrollFromShelfToShowAppListOverShelfApps) {
  base::HistogramTester histogram_tester;
  const int scroll_offset_threshold =
      ShelfConfig::Get()->mousewheel_scroll_offset_threshold() + 10;
  int bucket_scroll_count = 0;
  int bucket_swipe_count = 0;

  ShelfView* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
  const size_t max_app_count = 4;
  for (size_t app_count = 1; app_count <= max_app_count; ++app_count)
    AddApp();
  EXPECT_EQ(max_app_count, shelf_view->number_of_visible_apps());

  for (auto test : test_table) {
    GetShelfLayoutManager()->LayoutShelf();
    GetPrimaryShelf()->SetAlignment(test.alignment);
    ASSERT_EQ(test.alignment, GetPrimaryShelf()->alignment());

    // Direction of the swipe gesture depends on the shelf alignment and on the
    // event being a swipe or a fling.
    gfx::Vector2d offset = GetPrimaryShelf()->SelectValueForShelfAlignment(
        gfx::Vector2d(0, scroll_offset_threshold),
        gfx::Vector2d(-scroll_offset_threshold, 0),
        gfx::Vector2d(scroll_offset_threshold, 0));
    // Action performed on the edge closer to the home button should show the
    // bubble launcher.
    gfx::Point swipe_point;
    if (GetParam())
      swipe_point = GetHotseatWidget()->GetTargetBounds().right_center();
    else
      swipe_point = GetHotseatWidget()->GetTargetBounds().left_center();

    swipe_point = GetPrimaryShelf()->PrimaryAxisValue(
        swipe_point, GetHotseatWidget()->GetTargetBounds().top_center());

    if (test.swipe_gesture) {
      FlingBetweenLocations(swipe_point, swipe_point - offset);
      ++bucket_swipe_count;
    } else {
      DoTwoFingerScrollAtLocation(swipe_point, offset.x(), offset.y(), false);
      ++bucket_scroll_count;
    }

    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckVisibility(true);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kSwipeFromShelf,
                                       bucket_swipe_count);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kScrollFromShelf,
                                       bucket_scroll_count);

    // The same gesture on the opposite direction should dismiss the app list.
    if (test.swipe_gesture)
      FlingBetweenLocations(swipe_point, swipe_point + offset);
    else
      DoTwoFingerScrollAtLocation(swipe_point, -offset.x(), -offset.y(), false);

    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckVisibility(false);

    // Action performed over the hotseat should not show the bubble launcher
    swipe_point = GetHotseatWidget()->GetTargetBounds().CenterPoint();

    if (test.swipe_gesture) {
      FlingBetweenLocations(swipe_point, swipe_point - offset);
    } else {
      DoTwoFingerScrollAtLocation(swipe_point, offset.x(), offset.y(), false);
    }

    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckVisibility(false);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kSwipeFromShelf,
                                       bucket_swipe_count);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kScrollFromShelf,
                                       bucket_scroll_count);

    // Action performed on the edge farther to the home button should not show
    // the bubble launcher.
    if (GetParam())
      swipe_point = GetHotseatWidget()->GetTargetBounds().left_center();
    else
      swipe_point = GetHotseatWidget()->GetTargetBounds().right_center();

    swipe_point = GetPrimaryShelf()->PrimaryAxisValue(
        swipe_point, GetHotseatWidget()->GetTargetBounds().bottom_center());

    if (test.swipe_gesture) {
      FlingBetweenLocations(swipe_point, swipe_point - offset);
    } else {
      DoTwoFingerScrollAtLocation(swipe_point, offset.x(), offset.y(), false);
    }

    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckVisibility(false);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kSwipeFromShelf,
                                       bucket_swipe_count);
    histogram_tester.ExpectBucketCount("Apps.AppListBubbleShowSource",
                                       AppListShowSource::kScrollFromShelf,
                                       bucket_scroll_count);
  }
}

// TODO(https://crbug.com/1286875): This behavior is broken in production. An
// auto-hidden shelf will close after a short swipe up that fails to show the
// app list.
TEST_P(QuickActionShowBubbleTest,
       DISABLED_ShortSwipeUpOnAutoHideShelfKeepsShelfOpen) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  auto* generator = GetEventGenerator();
  constexpr base::TimeDelta kTimeDelta = base::Milliseconds(100);
  constexpr int kNumScrollSteps = 4;

  // Starts the drag from the center of the shelf's bottom.
  gfx::Rect shelf_bounds = GetVisibleShelfWidgetBoundsInScreen();
  gfx::Point start = shelf_bounds.bottom_center();

  // Create a normal unmaximized window, the auto-hide shelf should be hidden.
  aura::Window* window = CreateTestWindow();
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  window->Show();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Swiping up to show the auto-hide shelf.
  gfx::Point end = shelf_bounds.top_center();
  generator->GestureScrollSequence(start, end, kTimeDelta, kNumScrollSteps);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Swiping up on the auto-hide shelf to drag up the app list. Scroll Velocity
  // is not enough to show the app list but keep the previous shown auto-hide
  // shelf still visible. Starts fling from the navigation_widget.
  start = GetPrimaryShelf()
              ->navigation_widget()
              ->GetContentsView()
              ->GetBoundsInScreen()
              .CenterPoint();
  const int scroll_offset_threshold =
      ShelfConfig::Get()->mousewheel_scroll_offset_threshold() + 10;
  gfx::Vector2d offset(0, scroll_offset_threshold);
  end = start - offset;
  generator->GestureScrollSequence(
      start, end,
      generator->CalculateScrollDurationForFlingVelocity(start, end,
                                                         /*velocity =*/50, 4),
      4);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(false);
  // This line fails, see https://crbug.com/1286875.
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
}

// Tests that the shelf background is opaque in both screens after app list is
// dismissed in a secondary display. (See https://crbug.com/1060686)
TEST_F(ShelfLayoutManagerTest, ShelfBackgroundOpaqueAfterAppListUpdate) {
  UpdateDisplay("800x600,800x600");
  AppListControllerImpl* app_list_controller =
      Shell::Get()->app_list_controller();
  int64_t primary_display_id = display_manager()->GetDisplayAt(0).id();
  int64_t secondary_display_id = display_manager()->GetDisplayAt(1).id();

  app_list_controller->ToggleAppList(
      secondary_display_id, AppListShowSource::kShelfButton, base::TimeTicks());
  EXPECT_FALSE(app_list_controller->IsVisible(primary_display_id));
  EXPECT_TRUE(app_list_controller->IsVisible(secondary_display_id));

  app_list_controller->ToggleAppList(
      secondary_display_id, AppListShowSource::kShelfButton, base::TimeTicks());
  EXPECT_FALSE(app_list_controller->IsVisible(primary_display_id));
  EXPECT_FALSE(app_list_controller->IsVisible(secondary_display_id));

  auto* primary_root_window_controller =
      Shell::GetRootWindowControllerWithDisplayId(primary_display_id);
  auto* secondary_root_window_controller =
      Shell::GetRootWindowControllerWithDisplayId(secondary_display_id);

  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            primary_root_window_controller->shelf()
                ->shelf_layout_manager()
                ->shelf_background_type());
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            secondary_root_window_controller->shelf()
                ->shelf_layout_manager()
                ->shelf_background_type());
}

using NoSessionShelfLayoutManagerTest = NoSessionAshTestBase;

// Tests that shelf visibility is updated on login. (See
// https://crbug.com/1097464)
TEST_F(NoSessionShelfLayoutManagerTest, UpdateShelfVisibilityAfterLogin) {
  UpdateDisplay("1000x800");
  constexpr char kUser[] = "[email protected]";
  const AccountId kUserAccount = AccountId::FromUserEmail(kUser);

  // Setup autohide shelf pref.
  auto pref_service = std::make_unique<TestingPrefServiceSimple>();
  RegisterUserProfilePrefs(pref_service->registry(), /*country=*/"",
                           /*for_test=*/true);
  SetShelfAutoHideBehaviorPref(pref_service.get(),
                               WindowTreeHostManager::GetPrimaryDisplayId(),
                               ShelfAutoHideBehavior::kAlways);
  GetSessionControllerClient()->SetUserPrefService(kUserAccount,
                                                   std::move(pref_service));

  // Create a window that covers the full height of the in-session work area.
  const int kExpectedWindowHeight = 800 - ShelfConfig::Get()->shelf_size();
  auto window = CreateTestWindow(gfx::Rect(400, kExpectedWindowHeight));

  // Simulate login.
  SimulateUserLogin(kUser);

  // The window should be the same height.
  EXPECT_EQ(kExpectedWindowHeight, window->bounds().height());
}

// Test base for unit test related to shelf dimming.
class DimShelfLayoutManagerTestBase : public ShelfLayoutManagerTestBase {
 public:
  DimShelfLayoutManagerTestBase() = default;

  bool AutoDimEventHandlerInitialized() {
    return GetPrimaryShelf()->auto_dim_event_handler_.get();
  }

  bool ShelfDimmed() { return GetShelfLayoutManager()->dimmed_for_inactivity_; }

  void TriggerDimShelf() { GetPrimaryShelf()->DimShelf(); }

  void ResetDimShelf() { GetPrimaryShelf()->UndimShelf(); }

  bool HasDimShelfTimer() {
    return AutoDimEventHandlerInitialized() &&
           GetPrimaryShelf()->HasDimShelfTimer();
  }

  float GetWidgetOpacity(views::Widget* widget) {
    return widget->GetNativeView()->layer()->opacity();
  }

  // Expected opacity for floating shelf.
  const float kExpectedFloatingShelfDimOpacity = 0.74f;

  // Expected opacity for shelf when shelf is in the maximized state.
  const float kExpectedMaximizedShelfDimOpacity = 0.6f;

  // Expected opacity for shelf without dimming.
  const float kExpectedDefaultShelfOpacity = 1.0f;
};

// Paramaterized tests for shelf with and without shelf dimming enabled.
class DimShelfLayoutManagerTest : public DimShelfLayoutManagerTestBase,
                                  public testing::WithParamInterface<bool> {
 public:
  DimShelfLayoutManagerTest() = default;

  // testing::Test:
  void SetUp() override {
    if (GetParam()) {
      base::CommandLine::ForCurrentProcess()->AppendSwitch(
          switches::kEnableDimShelf);
    }
    DimShelfLayoutManagerTestBase::SetUp();
  }
};

// Used to test shelf dimming.
INSTANTIATE_TEST_SUITE_P(All, DimShelfLayoutManagerTest, testing::Bool());

// Tests that the auto dim handler is initialized and shelf is not dim on
// startup.
TEST_P(DimShelfLayoutManagerTest, AutoDimHandlerInitialized) {
  ASSERT_FALSE(ShelfDimmed());
  ASSERT_EQ(GetParam(), AutoDimEventHandlerInitialized());

  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->navigation_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->hotseat_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()->status_area_widget()),
      kExpectedDefaultShelfOpacity);
}

// Tests that the auto dim handler dims the shelf when called.
TEST_P(DimShelfLayoutManagerTest, AutoDimHandlerSetDim) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());
  ASSERT_FALSE(ShelfDimmed());
  if (!dim_shelf_enabled)
    return;
  TriggerDimShelf();
  ASSERT_TRUE(ShelfDimmed());
  ResetDimShelf();
  ASSERT_FALSE(ShelfDimmed());
}

// Tests that the auto dim handler sets the shelf opacity correctly for floating
// shelf.
TEST_P(DimShelfLayoutManagerTest, FloatingShelfDimAlpha) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());

  if (dim_shelf_enabled)
    TriggerDimShelf();

  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->hotseat_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->navigation_widget()),
            dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                              : kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetPrimaryShelf()->hotseat_widget()->GetShelfView()->layer()->opacity(),
      dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                        : kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()->status_area_widget()),
      dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                        : kExpectedDefaultShelfOpacity);
}

// Tests that the auto dim handler sets the shelf opacity correctly in the
// special case of maximized shelf.
TEST_P(DimShelfLayoutManagerTest, MaximizedShelfDimAlpha) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());
  ASSERT_FALSE(ShelfDimmed());

  if (dim_shelf_enabled) {
    views::Widget* widget = CreateTestWidget();
    widget->Maximize();
    TriggerDimShelf();
  }

  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->navigation_widget()),
            dim_shelf_enabled ? kExpectedMaximizedShelfDimOpacity
                              : kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->hotseat_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetPrimaryShelf()->hotseat_widget()->GetShelfView()->layer()->opacity(),
      dim_shelf_enabled ? kExpectedMaximizedShelfDimOpacity
                        : kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()->status_area_widget()),
      dim_shelf_enabled ? kExpectedMaximizedShelfDimOpacity
                        : kExpectedDefaultShelfOpacity);
}

// Tests that navigation and status area widgets are dimmed. Verifies the shelf
// view is not dimmed when the hotseat is in the kExtended state. Verifies that
// the shelf background/hotseat widget are not dimmed.
TEST_P(DimShelfLayoutManagerTest, InAppShelfDimAlpha) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());

  TabletModeControllerTestApi().EnterTabletMode();
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  ASSERT_FALSE(ShelfDimmed());

  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());
  SwipeUpOnShelf();
  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());

  if (dim_shelf_enabled)
    TriggerDimShelf();

  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->hotseat_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetPrimaryShelf()->hotseat_widget()->GetShelfView()->layer()->opacity(),
      kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->navigation_widget()),
            dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                              : kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()->status_area_widget()),
      dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                        : kExpectedDefaultShelfOpacity);
}

// Tests that shelf view, navigation widget, and status area widget are
// dimmed but the shelf background and hotseat are not.
TEST_P(DimShelfLayoutManagerTest, TabletModeHomeShelfDimAlpha) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());

  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_FALSE(ShelfDimmed());

  EXPECT_EQ(HotseatState::kShownHomeLauncher,
            GetShelfLayoutManager()->hotseat_state());

  if (dim_shelf_enabled)
    TriggerDimShelf();

  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->hotseat_widget()),
            kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetPrimaryShelf()->hotseat_widget()->GetShelfView()->layer()->opacity(),
      dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                        : kExpectedDefaultShelfOpacity);
  EXPECT_EQ(GetWidgetOpacity(GetPrimaryShelf()->navigation_widget()),
            dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                              : kExpectedDefaultShelfOpacity);
  EXPECT_EQ(
      GetWidgetOpacity(GetPrimaryShelf()->shelf_widget()->status_area_widget()),
      dim_shelf_enabled ? kExpectedFloatingShelfDimOpacity
                        : kExpectedDefaultShelfOpacity);
}

// Shelf dimming should not trigger when shelf is hidden in tablet mode.
TEST_P(DimShelfLayoutManagerTest, AutoHiddenShelfTabletModeDimAlpha) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());
  TabletModeControllerTestApi().EnterTabletMode();

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_FALSE(ShelfDimmed());
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  // Shelf should not be dimmed when auto hidden.
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  if (dim_shelf_enabled)
    TriggerDimShelf();
  EXPECT_FALSE(ShelfDimmed());

  // Minimizing the widget should show the shelf. The shelf can now be dimmed.
  widget->Minimize();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_FALSE(ShelfDimmed());
  if (dim_shelf_enabled)
    TriggerDimShelf();
  EXPECT_EQ(dim_shelf_enabled, ShelfDimmed());
}

// Shelf dimming should not trigger when shelf is hidden in clamshell mode.
TEST_P(DimShelfLayoutManagerTest, AutoHiddenShelfClamshellModeDimAlpha) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_FALSE(ShelfDimmed());
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  // Shelf should not be dimmed when auto hidden. The dim shelf timer should
  // persist after failing to dim the shelf.
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  if (dim_shelf_enabled)
    TriggerDimShelf();
  EXPECT_FALSE(ShelfDimmed());
  EXPECT_EQ(dim_shelf_enabled, HasDimShelfTimer());

  // Minimizing the widget should show the shelf. The shelf can now be dimmed
  // and the dim shelf timer should no longer be active.
  widget->Minimize();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  ASSERT_FALSE(ShelfDimmed());
  EXPECT_EQ(dim_shelf_enabled, HasDimShelfTimer());
  if (dim_shelf_enabled)
    TriggerDimShelf();
  EXPECT_EQ(dim_shelf_enabled, ShelfDimmed());
  EXPECT_FALSE(HasDimShelfTimer());
}

// Shelf should be undimmed when transitioning into the visible state and create
// a dim shelf timer.
TEST_P(DimShelfLayoutManagerTest, AutoHiddenShelfUndimOnShow) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_FALSE(ShelfDimmed());
  if (dim_shelf_enabled)
    TriggerDimShelf();
  EXPECT_EQ(dim_shelf_enabled, ShelfDimmed());
  views::Widget* widget = CreateTestWidget();

  // Maximize and minimize the widget to cycle between shelf auto hidden states.
  widget->Maximize();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  widget->Minimize();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Hiding and showing the auto hidden shelf should set the shelf to the
  // undimmed state but also create a dim shelf timer.
  ASSERT_FALSE(ShelfDimmed());
  EXPECT_EQ(dim_shelf_enabled, HasDimShelfTimer());
  if (dim_shelf_enabled)
    TriggerDimShelf();
  EXPECT_EQ(dim_shelf_enabled, ShelfDimmed());
  EXPECT_FALSE(HasDimShelfTimer());
}

// Shelf should be undimmed when auto hidden shelf is disabled.
TEST_P(DimShelfLayoutManagerTest, AutoHiddenShelfUndimOnDisable) {
  const bool dim_shelf_enabled = GetParam();
  ASSERT_EQ(dim_shelf_enabled, AutoDimEventHandlerInitialized());

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_FALSE(ShelfDimmed());
  if (dim_shelf_enabled)
    TriggerDimShelf();
  EXPECT_EQ(dim_shelf_enabled, ShelfDimmed());

  // Create and maximize a widget to cycle force auto hidden shelf.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Disabling auto hidden shelf should undim the shelf.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  ASSERT_FALSE(ShelfDimmed());
}

class NavigationWidgetRTLTest
    : public ShelfLayoutManagerTest,
      public testing::WithParamInterface<
          std::tuple</*in_tablet=*/bool, /*in_rtl=*/bool>> {
 public:
  NavigationWidgetRTLTest()
      : in_tablet_(std::get<0>(GetParam())),
        in_rtl_(std::get<1>(GetParam())),
        scoped_locale_(in_rtl_ ? "ar" : "") {}
  ~NavigationWidgetRTLTest() override = default;

  // Indicates whether the test should run in the tablet mode.
  const bool in_tablet_;

  // Indicates whether the test should run under RTL.
  const bool in_rtl_;

  base::test::ScopedRestoreICUDefaultLocale scoped_locale_;
};

INSTANTIATE_TEST_SUITE_P(ALL,
                         NavigationWidgetRTLTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(NavigationWidgetRTLTest, VerifyHomeButtonBounds) {
  if (in_tablet_) {
    Shell::Get()
        ->accessibility_controller()
        ->SetTabletModeShelfNavigationButtonsEnabled(true);
    TabletModeControllerTestApi().EnterTabletMode();
    ASSERT_EQ(HotseatState::kShownHomeLauncher,
              GetShelfLayoutManager()->hotseat_state());
  } else {
    ASSERT_EQ(HotseatState::kShownClamshell,
              GetShelfLayoutManager()->hotseat_state());
  }

  const gfx::Rect display_bounds = GetPrimaryDisplay().bounds();
  Shelf* shelf = GetPrimaryShelf();
  ShelfNavigationWidget* navigation_widget = shelf->navigation_widget();

  auto fetch_home_button_screen_bounds =
      [](const ShelfNavigationWidget* navigation_widget,
         bool in_rtl) -> gfx::Rect {
    gfx::Rect home_button_bounds_in_widget =
        navigation_widget->bounds_animator_for_test()->GetTargetBounds(
            navigation_widget->GetHomeButton());
    if (in_rtl) {
      home_button_bounds_in_widget =
          navigation_widget->GetRootView()->GetMirroredRect(
              home_button_bounds_in_widget);
    }
    gfx::Rect home_button_bounds_in_screen = home_button_bounds_in_widget;
    home_button_bounds_in_screen.Offset(
        navigation_widget->GetWindowBoundsInScreen().OffsetFromOrigin());
    return home_button_bounds_in_screen;
  };

  // Verify home button bounds in home launcher.
  {
    const gfx::Rect home_button_bounds_in_screen =
        fetch_home_button_screen_bounds(navigation_widget, in_rtl_);
    const int horizontal_edge_spacing =
        ShelfConfig::Get()->control_button_edge_spacing(
            /*is_primary_axis_edge=*/true);
    EXPECT_EQ(
        horizontal_edge_spacing,
        in_rtl_ ? display_bounds.right() - home_button_bounds_in_screen.right()
                : home_button_bounds_in_screen.x());
    const int vertical_edge_spacing =
        ShelfConfig::Get()->control_button_edge_spacing(
            /*is_primary_axis_edge=*/false);
    EXPECT_EQ(display_bounds.bottom(),
              home_button_bounds_in_screen.bottom() + vertical_edge_spacing);

    auto* home_button = navigation_widget->GetHomeButton();
    ASSERT_EQ(views::Button::STATE_NORMAL, home_button->GetState());
    GetEventGenerator()->MoveMouseTo(
        home_button_bounds_in_screen.CenterPoint());
    EXPECT_EQ(views::Button::STATE_HOVERED, home_button->GetState());
  }

  if (!in_tablet_)
    return;

  // The test code below is only for the tablet mode.

  // Activate a window and wait for the navigation widget animation to finish.
  views::WidgetAnimationWaiter waiter(shelf->navigation_widget());
  std::unique_ptr<aura::Window> window =
      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  wm::ActivateWindow(window.get());
  waiter.WaitForAnimation();

  ASSERT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());

  // Verify home button bounds in the hidden state.
  {
    const gfx::Rect home_button_bounds_in_screen =
        fetch_home_button_screen_bounds(navigation_widget, in_rtl_);
    EXPECT_EQ(display_bounds.bottom(), home_button_bounds_in_screen.bottom());
  }
}

class ShelfLayoutManagerWithEcheTest : public ShelfLayoutManagerTestBase {
 public:
  template <typename... TaskEnvironmentTraits>
  explicit ShelfLayoutManagerWithEcheTest(TaskEnvironmentTraits&&... traits)
      : ShelfLayoutManagerTestBase(
            std::forward<TaskEnvironmentTraits>(traits)...) {
    scoped_feature_list_.InitAndEnableFeature(features::kEcheSWA);
  }

 protected:
  void SetUp() override { ShelfLayoutManagerTestBase::SetUp(); }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  // Calling the factory constructor is enough to set it up.
  TestAshWebViewFactory test_web_view_factory_;
};

TEST_F(ShelfLayoutManagerWithEcheTest, AutoHideShelfWithEcheHidden) {
  // Create and maximize a widget to cycle force auto hidden shelf.
  views::Widget* widget = CreateTestWidget();
  widget->Maximize();
  // Set the shelf to auto-hide.
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  GetShelfLayoutManager()->UpdateVisibilityState(/*force_layout=*/false);

  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Create and show Eche Bubble.
  StatusAreaWidget* status_area =
      StatusAreaWidgetTestHelper::GetStatusAreaWidget();
  SkBitmap bitmap;
  bitmap.allocN32Pixels(30, 30);
  gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
  image_skia.MakeThreadSafe();
  status_area->eche_tray()->LoadBubble(
      GURL("http://google.com"), gfx::Image(image_skia), u"app 1",
      u"your phone",
      eche_app::mojom::ConnectionStatus::kConnectionStatusDisconnected,
      eche_app::mojom::AppStreamLaunchEntryPoint::RECENT_APPS);
  status_area->eche_tray()->ShowBubble();
  UpdateAutoHideStateNow();

  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Hide Eche Bubble.
  status_area->eche_tray()->HideBubble();
  UpdateAutoHideStateNow();

  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}

}  // namespace ash