chromium/ash/capture_mode/capture_mode_unittests.cc

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

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

#include "ash/accessibility/magnifier/docked_magnifier_controller.h"
#include "ash/accessibility/magnifier/magnifier_glass.h"
#include "ash/annotator/annotation_tray.h"
#include "ash/annotator/annotations_overlay_controller.h"
#include "ash/annotator/annotator_controller.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/capture_mode/capture_mode_bar_view.h"
#include "ash/capture_mode/capture_mode_behavior.h"
#include "ash/capture_mode/capture_mode_constants.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/capture_mode/capture_mode_menu_group.h"
#include "ash/capture_mode/capture_mode_menu_toggle_button.h"
#include "ash/capture_mode/capture_mode_metrics.h"
#include "ash/capture_mode/capture_mode_session.h"
#include "ash/capture_mode/capture_mode_session_focus_cycler.h"
#include "ash/capture_mode/capture_mode_session_test_api.h"
#include "ash/capture_mode/capture_mode_settings_test_api.h"
#include "ash/capture_mode/capture_mode_settings_view.h"
#include "ash/capture_mode/capture_mode_source_view.h"
#include "ash/capture_mode/capture_mode_test_util.h"
#include "ash/capture_mode/capture_mode_type_view.h"
#include "ash/capture_mode/capture_mode_types.h"
#include "ash/capture_mode/capture_mode_util.h"
#include "ash/capture_mode/fake_folder_selection_dialog_factory.h"
#include "ash/capture_mode/stop_recording_button_tray.h"
#include "ash/capture_mode/test_capture_mode_delegate.h"
#include "ash/capture_mode/user_nudge_controller.h"
#include "ash/capture_mode/video_recording_watcher.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/display/cursor_window_controller.h"
#include "ash/display/output_protection_delegate.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/projector/projector_controller_impl.h"
#include "ash/projector/projector_metrics.h"
#include "ash/public/cpp/capture_mode/capture_mode_test_api.h"
#include "ash/public/cpp/holding_space/holding_space_test_api.h"
#include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
#include "ash/public/cpp/projector/projector_session.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/test/mock_projector_client.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/style/icon_button.h"
#include "ash/style/tab_slider_button.h"
#include "ash/system/status_area_widget.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_util.h"
#include "ash/test/test_widget_builder.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.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 "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/safe_base_name.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "chromeos/ash/services/recording/recording_service_test_api.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
#include "chromeos/ui/frame/frame_header.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user_type.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/capture_client_observer.h"
#include "ui/aura/client/cursor_shape_client.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/cursor_factory.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/util/display_util.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_handler.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_modality_controller.h"
#include "ui/wm/core/window_util.h"

namespace ash {

namespace {

using ::ui::mojom::CursorType;

constexpr char kEndRecordingReasonInClamshellHistogramName[] =
    "Ash.CaptureModeController.EndRecordingReason.ClamshellMode";

// Returns true if the software-composited cursor is enabled.
bool IsCursorCompositingEnabled() {
  return Shell::Get()
      ->window_tree_host_manager()
      ->cursor_window_controller()
      ->is_cursor_compositing_enabled();
}

// Sets up a callback that will be triggered when a capture file (image or
// video) is deleted as a result of a user action. The callback will verify the
// successful deletion of the file, and will quit the given `loop`.
void SetUpFileDeletionVerifier(base::RunLoop* loop) {
  DCHECK(loop);
  CaptureModeTestApi().SetOnCaptureFileDeletedCallback(
      base::BindLambdaForTesting(
          [loop](const base::FilePath& path, bool delete_successful) {
            EXPECT_TRUE(delete_successful);
            base::ScopedAllowBlockingForTesting allow_blocking;
            EXPECT_FALSE(base::PathExists(path));
            loop->Quit();
          }));
}

// Defines a capture client observer, that sets the input capture to the window
// given to the constructor, and destroys it once capture is lost.
class TestCaptureClientObserver : public aura::client::CaptureClientObserver {
 public:
  explicit TestCaptureClientObserver(std::unique_ptr<aura::Window> window)
      : window_(std::move(window)) {
    DCHECK(window_);
    auto* capture_client =
        aura::client::GetCaptureClient(window_->GetRootWindow());
    capture_client->SetCapture(window_.get());
    capture_client->AddObserver(this);
  }

  ~TestCaptureClientObserver() override { StopObserving(); }

  // aura::client::CaptureClientObserver:
  void OnCaptureChanged(aura::Window* lost_capture,
                        aura::Window* gained_capture) override {
    if (lost_capture != window_.get())
      return;

    StopObserving();
    window_.reset();
  }

 private:
  void StopObserving() {
    if (!window_)
      return;

    auto* capture_client =
        aura::client::GetCaptureClient(window_->GetRootWindow());
    capture_client->RemoveObserver(this);
  }

  std::unique_ptr<aura::Window> window_;
};

}  // namespace

class CaptureModeTest : public AshTestBase {
 public:
  CaptureModeTest() = default;
  explicit CaptureModeTest(base::test::TaskEnvironment::TimeSource time)
      : AshTestBase(time) {}
  CaptureModeTest(const CaptureModeTest&) = delete;
  CaptureModeTest& operator=(const CaptureModeTest&) = delete;
  ~CaptureModeTest() override = default;

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();
  }

  views::Widget* GetCaptureModeLabelWidget() const {
    auto* session = CaptureModeController::Get()->capture_mode_session();
    DCHECK(session);
    return CaptureModeSessionTestApi(session).GetCaptureLabelWidget();
  }

  CaptureModeSettingsView* GetCaptureModeSettingsView() const {
    auto* session = CaptureModeController::Get()->capture_mode_session();
    DCHECK(session);
    return CaptureModeSessionTestApi(session).GetCaptureModeSettingsView();
  }

  views::Widget* GetCaptureModeSettingsWidget() const {
    auto* session = CaptureModeController::Get()->capture_mode_session();
    DCHECK(session);
    return CaptureModeSessionTestApi(session).GetCaptureModeSettingsWidget();
  }

  bool IsFolderSelectionDialogShown() const {
    auto* session = CaptureModeController::Get()->capture_mode_session();
    DCHECK(session);
    return CaptureModeSessionTestApi(session).IsFolderSelectionDialogShown();
  }

  bool AreAllCaptureSessionUisVisible() const {
    auto* session = CaptureModeController::Get()->capture_mode_session();
    DCHECK(session);
    return CaptureModeSessionTestApi(session).AreAllUisVisible();
  }

  aura::Window* GetDimensionsLabelWindow() const {
    auto* controller = CaptureModeController::Get();
    DCHECK(controller->IsActive());
    auto* widget = CaptureModeSessionTestApi(controller->capture_mode_session())
                       .GetDimensionsLabelWidget();
    return widget ? widget->GetNativeWindow() : nullptr;
  }

  std::optional<gfx::Point> GetMagnifierGlassCenterPoint() const {
    auto* controller = CaptureModeController::Get();
    DCHECK(controller->IsActive());
    auto& magnifier =
        CaptureModeSessionTestApi(controller->capture_mode_session())
            .GetMagnifierGlass();
    if (magnifier.host_widget_for_testing()) {
      return magnifier.host_widget_for_testing()
          ->GetWindowBoundsInScreen()
          .CenterPoint();
    }
    return std::nullopt;
  }

  // Start Capture Mode with source region and type image.
  CaptureModeController* StartImageRegionCapture() {
    return StartCaptureSession(CaptureModeSource::kRegion,
                               CaptureModeType::kImage);
  }

  CaptureModeController* StartSessionAndRecordWindow(aura::Window* window) {
    auto* controller = StartCaptureSession(CaptureModeSource::kWindow,
                                           CaptureModeType::kVideo);
    GetEventGenerator()->MoveMouseToCenterOf(window);
    StartVideoRecordingImmediately();
    EXPECT_TRUE(controller->is_recording_in_progress());
    return controller;
  }

  // Select a region by pressing and dragging the mouse.
  void SelectRegion(const gfx::Rect& region_in_screen,
                    bool release_mouse = true) {
    SelectCaptureModeRegion(GetEventGenerator(), region_in_screen,
                            release_mouse);
  }

  void WaitForSessionToEnd() {
    auto* controller = CaptureModeController::Get();
    if (!controller->IsActive())
      return;

    auto* test_delegate = static_cast<TestCaptureModeDelegate*>(
        controller->delegate_for_testing());
    ASSERT_TRUE(test_delegate);
    base::RunLoop run_loop;
    test_delegate->set_on_session_state_changed_callback(
        run_loop.QuitClosure());
    run_loop.Run();
    ASSERT_FALSE(controller->IsActive());
  }

  void RemoveSecondaryDisplay() {
    const int64_t primary_id = WindowTreeHostManager::GetPrimaryDisplayId();
    display::ManagedDisplayInfo primary_info =
        display_manager()->GetDisplayInfo(primary_id);
    std::vector<display::ManagedDisplayInfo> display_info_list;
    display_info_list.push_back(primary_info);
    display_manager()->OnNativeDisplaysChanged(display_info_list);

    // Spin the run loop so that we get a signal that the associated root window
    // of the removed display is destroyed.
    base::RunLoop().RunUntilIdle();
  }

  void SwitchToUser2() {
    auto* session_controller = GetSessionControllerClient();
    constexpr char kUserEmail[] = "user2@capture_mode";
    session_controller->AddUserSession(kUserEmail);
    session_controller->SwitchActiveUser(AccountId::FromUserEmail(kUserEmail));
  }

  void OpenSettingsView() {
    auto* session = static_cast<CaptureModeSession*>(
        CaptureModeController::Get()->capture_mode_session());
    DCHECK(session);
    ASSERT_EQ(session->session_type(), SessionType::kReal);
    ClickOnView(CaptureModeSessionTestApi(session)
                    .GetCaptureModeBarView()
                    ->settings_button(),
                GetEventGenerator());
  }

  std::unique_ptr<aura::Window> CreateTransientModalChildWindow(
      gfx::Rect child_window_bounds,
      aura::Window* transient_parent) {
    auto child = CreateTestWindow(child_window_bounds);
    wm::AddTransientChild(transient_parent, child.get());
    child->Show();

    child->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kWindow);
    wm::SetModalParent(child.get(), transient_parent);
    return child;
  }

  void VerifyOverlayWindow(aura::Window* overlay_window,
                           CaptureModeSource source,
                           const gfx::Rect user_region) {
    VerifyOverlayWindowForCaptureMode(overlay_window, GetWindowBeingRecorded(),
                                      source, user_region);
  }

  aura::Window* GetWindowBeingRecorded() const {
    auto* controller = CaptureModeController::Get();
    DCHECK(controller->is_recording_in_progress());
    return controller->video_recording_watcher_for_testing()
        ->window_being_recorded();
  }
};

class CaptureSessionWidgetClosed {
 public:
  explicit CaptureSessionWidgetClosed(views::Widget* widget) {
    DCHECK(widget);
    widget_ = widget->GetWeakPtr();
  }
  CaptureSessionWidgetClosed(const CaptureSessionWidgetClosed&) = delete;
  CaptureSessionWidgetClosed& operator=(const CaptureSessionWidgetClosed&) =
      delete;
  ~CaptureSessionWidgetClosed() = default;

  bool GetWidgetClosed() const { return !widget_ || widget_->IsClosed(); }

 private:
  base::WeakPtr<views::Widget> widget_;
};

TEST_F(CaptureModeTest, StartStop) {
  auto* controller = CaptureModeController::Get();
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(controller->IsActive());
  // Calling start again is a no-op.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(controller->IsActive());

  // Closing the session should close the native window of capture mode bar
  // immediately.
  auto* bar_window = GetCaptureModeBarWidget()->GetNativeWindow();
  aura::WindowTracker tracker({bar_window});
  controller->Stop();
  EXPECT_TRUE(tracker.windows().empty());
  EXPECT_FALSE(controller->IsActive());
}

TEST_F(CaptureModeTest, CheckCursorVisibility) {
  // Hide cursor before entering capture mode.
  auto* cursor_manager = Shell::Get()->cursor_manager();
  cursor_manager->SetCursor(CursorType::kPointer);
  cursor_manager->HideCursor();
  cursor_manager->DisableMouseEvents();
  EXPECT_FALSE(cursor_manager->IsCursorVisible());

  auto* controller = CaptureModeController::Get();
  controller->Start(CaptureModeEntryType::kQuickSettings);
  // After capture mode initialization, cursor should be visible.
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());

  // Enter tablet mode.
  SwitchToTabletMode();
  // After entering tablet mode, cursor should be invisible and locked.
  EXPECT_FALSE(cursor_manager->IsCursorVisible());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());

  // Leave tablet mode, cursor should be visible again.
  LeaveTabletMode();
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
}

TEST_F(CaptureModeTest, CheckCursorVisibilityOnTabletMode) {
  auto* cursor_manager = Shell::Get()->cursor_manager();

  // Enter tablet mode.
  SwitchToTabletMode();
  // After entering tablet mode, cursor should be invisible.
  EXPECT_FALSE(cursor_manager->IsCursorVisible());

  // Open capture mode.
  auto* controller = CaptureModeController::Get();
  controller->Start(CaptureModeEntryType::kQuickSettings);
  // Cursor should be invisible since it's still in tablet mode.
  EXPECT_FALSE(cursor_manager->IsCursorVisible());

  // Leave tablet mode, cursor should be visible now.
  LeaveTabletMode();
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
}

// Regression test for https://crbug.com/1172425.
TEST_F(CaptureModeTest, NoCrashOnClearingCapture) {
  TestCaptureClientObserver observer(CreateTestWindow(gfx::Rect(200, 200)));
  auto* controller = StartImageRegionCapture();
  EXPECT_TRUE(controller->IsActive());
}

TEST_F(CaptureModeTest, CheckWidgetClosed) {
  auto* controller = CaptureModeController::Get();
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(controller->IsActive());
  EXPECT_TRUE(GetCaptureModeBarWidget());
  CaptureSessionWidgetClosed observer(GetCaptureModeBarWidget());
  EXPECT_FALSE(observer.GetWidgetClosed());
  controller->Stop();
  EXPECT_FALSE(controller->IsActive());
  EXPECT_FALSE(controller->capture_mode_session());
  // The Widget should have been destroyed by now.
  EXPECT_TRUE(observer.GetWidgetClosed());
}

TEST_F(CaptureModeTest, StartWithMostRecentTypeAndSource) {
  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kFullscreen);
  controller->SetType(CaptureModeType::kVideo);
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(controller->IsActive());

  EXPECT_FALSE(GetImageToggleButton()->selected());
  EXPECT_TRUE(GetVideoToggleButton()->selected());
  EXPECT_TRUE(GetFullscreenToggleButton()->selected());
  EXPECT_FALSE(GetRegionToggleButton()->selected());
  EXPECT_FALSE(GetWindowToggleButton()->selected());

  ClickOnView(GetCloseButton(), GetEventGenerator());
  EXPECT_FALSE(controller->IsActive());
}

TEST_F(CaptureModeTest, AccessibleCheckedState) {
  auto* controller = CaptureModeController::Get();
  controller->Start(CaptureModeEntryType::kQuickSettings);
  ui::AXNodeData data;
  GetImageToggleButton()->SetSelected(true);
  GetImageToggleButton()->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kTrue);

  data = ui::AXNodeData();
  GetImageToggleButton()->SetSelected(false);
  GetImageToggleButton()->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kFalse);
}

TEST_F(CaptureModeTest, ChangeTypeAndSourceFromUI) {
  auto* controller = CaptureModeController::Get();
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(controller->IsActive());

  EXPECT_TRUE(GetImageToggleButton()->selected());
  EXPECT_FALSE(GetVideoToggleButton()->selected());
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetVideoToggleButton(), event_generator);
  EXPECT_FALSE(GetImageToggleButton()->selected());
  EXPECT_TRUE(GetVideoToggleButton()->selected());
  EXPECT_EQ(controller->type(), CaptureModeType::kVideo);

  ClickOnView(GetWindowToggleButton(), event_generator);
  EXPECT_FALSE(GetFullscreenToggleButton()->selected());
  EXPECT_FALSE(GetRegionToggleButton()->selected());
  EXPECT_TRUE(GetWindowToggleButton()->selected());
  EXPECT_EQ(controller->source(), CaptureModeSource::kWindow);

  ClickOnView(GetFullscreenToggleButton(), event_generator);
  EXPECT_TRUE(GetFullscreenToggleButton()->selected());
  EXPECT_FALSE(GetRegionToggleButton()->selected());
  EXPECT_FALSE(GetWindowToggleButton()->selected());
  EXPECT_EQ(controller->source(), CaptureModeSource::kFullscreen);
}

TEST_F(CaptureModeTest, VideoRecordingUiBehavior) {
  // Start Capture Mode in a fullscreen video recording mode.
  CaptureModeController* controller = StartCaptureSession(
      CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  EXPECT_TRUE(controller->IsActive());
  EXPECT_FALSE(controller->is_recording_in_progress());
  EXPECT_FALSE(IsCursorCompositingEnabled());

  // Hit Enter to begin recording.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_EQ(CursorType::kPointer,
            Shell::Get()->cursor_manager()->GetCursor().type());
  WaitForRecordingToStart();
  EXPECT_FALSE(controller->IsActive());
  EXPECT_TRUE(controller->is_recording_in_progress());

  // The composited cursor should remain disabled now that we're using the
  // cursor overlay on the capturer. The stop-recording button should show up in
  // the status area widget.
  EXPECT_FALSE(IsCursorCompositingEnabled());
  auto* stop_recording_button = Shell::GetPrimaryRootWindowController()
                                    ->GetStatusAreaWidget()
                                    ->stop_recording_button_tray();
  EXPECT_TRUE(stop_recording_button->visible_preferred());

  // End recording via the stop-recording button. Expect that it's now hidden.
  base::HistogramTester histogram_tester;
  ClickOnView(stop_recording_button, event_generator);
  EXPECT_FALSE(stop_recording_button->visible_preferred());
  EXPECT_FALSE(controller->is_recording_in_progress());
  EXPECT_FALSE(IsCursorCompositingEnabled());
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kStopRecordingButton, 1);
}

TEST_F(CaptureModeTest, NoCrashOnMultipleClicksOnStopRecordingButton) {
  ash::CaptureModeTestApi test_api;
  test_api.StartForFullscreen(/*for_video=*/true);
  test_api.PerformCapture();
  test_api.FlushRecordingServiceForTesting();

  auto* stop_recording_button = Shell::GetPrimaryRootWindowController()
                                    ->GetStatusAreaWidget()
                                    ->stop_recording_button_tray();
  EXPECT_TRUE(stop_recording_button->visible_preferred());

  // Use slow animations so that the stop recording button takes much longer to
  // hide, so it's easier to repro the crash at http://b/270625738.
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);

  LeftClickOn(stop_recording_button);
  test_api.FlushRecordingServiceForTesting();

  // There should be no crash on the second click.
  LeftClickOn(stop_recording_button);
}

// Tests the behavior of repositioning a region with capture mode.
TEST_F(CaptureModeTest, CaptureRegionRepositionBehavior) {
  // Use a set display size as we will be choosing points in this test.
  UpdateDisplay("800x700");

  auto* controller = StartImageRegionCapture();

  // The first time selecting a region, the region is a default rect.
  EXPECT_EQ(gfx::Rect(), controller->user_capture_region());

  // Press down and drag to select a region.
  SelectRegion(gfx::Rect(100, 100, 600, 600));

  // Click somewhere in the center on the region and drag. The whole region
  // should move. Note that the point cannot be in the capture button bounds,
  // which is located in the center of the region.
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(gfx::Point(200, 200));
  event_generator->DragMouseBy(-50, -50);
  EXPECT_EQ(gfx::Rect(50, 50, 600, 600), controller->user_capture_region());

  // Try to drag the region offscreen. The region should be bound by the display
  // size.
  event_generator->set_current_screen_location(gfx::Point(100, 100));
  event_generator->DragMouseBy(-150, -150);
  EXPECT_EQ(gfx::Rect(600, 600), controller->user_capture_region());
}

// Tests the behavior of resizing a region with capture mode using the corner
// drag affordances.
TEST_F(CaptureModeTest, CaptureRegionCornerResizeBehavior) {
  // Use a set display size as we will be choosing points in this test.
  UpdateDisplay("800x700");

  auto* controller = StartImageRegionCapture();
  // Create the initial region.
  const gfx::Rect target_region(gfx::Rect(200, 200, 400, 400));
  SelectRegion(target_region);

  // For each corner point try dragging to several points and verify that the
  // capture region is as expected.
  struct {
    std::string trace;
    gfx::Point drag_point;
    // The point that stays the same while dragging. It is the opposite vertex
    // to |drag_point| on |target_region|.
    gfx::Point anchor_point;
  } kDragCornerCases[] = {
      {"origin", target_region.origin(), target_region.bottom_right()},
      {"top_right", target_region.top_right(), target_region.bottom_left()},
      {"bottom_right", target_region.bottom_right(), target_region.origin()},
      {"bottom_left", target_region.bottom_left(), target_region.top_right()},
  };

  // The test corner points are one in each corner outside |target_region| and
  // one point inside |target_region|.
  auto drag_test_points = {gfx::Point(100, 100), gfx::Point(700, 100),
                           gfx::Point(700, 700), gfx::Point(100, 700),
                           gfx::Point(400, 400)};
  auto* event_generator = GetEventGenerator();
  for (auto test_case : kDragCornerCases) {
    SCOPED_TRACE(test_case.trace);
    event_generator->set_current_screen_location(test_case.drag_point);
    event_generator->PressLeftButton();

    // At each drag test point, the region rect should be the rect created by
    // the given |corner_point| and the drag test point. That is, the width
    // should match the x distance between the two points, the height should
    // match the y distance between the two points and that both points are
    // contained in the region.
    for (auto drag_test_point : drag_test_points) {
      event_generator->MoveMouseTo(drag_test_point);
      gfx::Rect region = controller->user_capture_region();
      const gfx::Vector2d distance = test_case.anchor_point - drag_test_point;
      EXPECT_EQ(std::abs(distance.x()), region.width());
      EXPECT_EQ(std::abs(distance.y()), region.height());

      // gfx::Rect::Contains returns the point (x+width, y+height) as false, so
      // make the region one unit bigger to account for this.
      region.Inset(gfx::Insets(-1));
      EXPECT_TRUE(region.Contains(drag_test_point));
      EXPECT_TRUE(region.Contains(test_case.anchor_point));
    }

    // Make sure the region is reset for the next iteration.
    event_generator->MoveMouseTo(test_case.drag_point);
    event_generator->ReleaseLeftButton();
    ASSERT_EQ(target_region, controller->user_capture_region());
  }
}

// Tests the behavior of resizing a region with capture mode using the edge drag
// affordances.
TEST_F(CaptureModeTest, CaptureRegionEdgeResizeBehavior) {
  // Use a set display size as we will be choosing points in this test.
  UpdateDisplay("800x700");

  auto* controller = StartImageRegionCapture();
  // Create the initial region.
  const gfx::Rect target_region(gfx::Rect(200, 200, 200, 200));
  SelectRegion(target_region);

  // For each edge point try dragging to several points and verify that the
  // capture region is as expected.
  struct DragEdgeCase {
    std::string trace;
    gfx::Point drag_point;
    // True if horizontal direction (left, right). Height stays the same while
    // dragging if true, width stays the same while dragging if false.
    bool horizontal;
    // The edge that stays the same while dragging. It is the opposite edge to
    // |drag_point|. For example, if |drag_point| is the left center of
    // |target_region|, then |anchor_edge| is the right edge.
    int anchor_edge;
  };

  // Cases where the drag starts in the center of the edge, i.e., at the
  // indicator circles.
  std::vector<DragEdgeCase> drag_edge_cases = {
      {"left", target_region.left_center(), true, target_region.right()},
      {"top", target_region.top_center(), false, target_region.bottom()},
      {"right", target_region.right_center(), true, target_region.x()},
      {"bottom", target_region.bottom_center(), false, target_region.y()},
  };

  // Append cases where the drag starts along the edge but not at the circles.
  std::vector<DragEdgeCase> offset_cases = {};
  for (auto center_case : drag_edge_cases) {
    DragEdgeCase new_case(center_case);
    center_case.horizontal ? new_case.drag_point.Offset(0, 25)
                           : new_case.drag_point.Offset(25, 0);
    offset_cases.push_back(new_case);
  }
  drag_edge_cases.insert(drag_edge_cases.end(), offset_cases.begin(),
                         offset_cases.end());

  // Drag to a couple of points that change both x and y. In all these cases,
  // only the width or height should change.
  auto drag_test_points = {gfx::Point(150, 150), gfx::Point(350, 350),
                           gfx::Point(450, 450)};
  auto* event_generator = GetEventGenerator();
  for (auto test_case : drag_edge_cases) {
    SCOPED_TRACE(test_case.trace);
    event_generator->set_current_screen_location(test_case.drag_point);
    event_generator->PressLeftButton();

    for (auto drag_test_point : drag_test_points) {
      event_generator->MoveMouseTo(drag_test_point);
      const gfx::Rect region = controller->user_capture_region();

      // One of width/height will always be the same as |target_region|'s
      // initial width/height, depending on the edge affordance. The other
      // dimension will be the distance from |drag_test_point| to the anchor
      // edge.
      const int variable_length = std::abs(
          (test_case.horizontal ? drag_test_point.x() : drag_test_point.y()) -
          test_case.anchor_edge);
      const int expected_width =
          test_case.horizontal ? variable_length : target_region.width();
      const int expected_height =
          test_case.horizontal ? target_region.height() : variable_length;

      EXPECT_EQ(expected_width, region.width());
      EXPECT_EQ(expected_height, region.height());
    }

    // Make sure the region is reset for the next iteration.
    event_generator->MoveMouseTo(test_case.drag_point);
    event_generator->ReleaseLeftButton();
    ASSERT_EQ(target_region, controller->user_capture_region());
  }
}

// Tests that the capture region persists after exiting and reentering capture
// mode.
TEST_F(CaptureModeTest, CaptureRegionPersistsAfterExit) {
  auto* controller = StartImageRegionCapture();
  const gfx::Rect region(100, 100, 200, 200);
  SelectRegion(region);

  controller->Stop();
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_EQ(region, controller->user_capture_region());
}

// Tests that the capture region resets when clicking outside the current
// capture regions bounds.
TEST_F(CaptureModeTest, CaptureRegionResetsOnClickOutside) {
  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(100, 100, 200, 200));

  // Click on an area outside of the current capture region. The capture region
  // should reset to default rect.
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(gfx::Point(400, 400));
  event_generator->ClickLeftButton();
  EXPECT_EQ(gfx::Rect(), controller->user_capture_region());
}

// Tests that buttons on the capture mode bar still work when a region is
// "covering" them.
TEST_F(CaptureModeTest, CaptureRegionCoversCaptureModeBar) {
  UpdateDisplay("800x700");

  auto* controller = StartImageRegionCapture();

  // Select a region such that the capture mode bar is covered.
  SelectRegion(gfx::Rect(5, 5, 795, 695));
  EXPECT_TRUE(controller->user_capture_region().Contains(
      GetCaptureModeBarView()->GetBoundsInScreen()));

  // Click on the fullscreen toggle button to verify that we enter fullscreen
  // capture mode. Then click on the region toggle button to verify that we
  // reenter region capture mode and that the region is still covering the
  // capture mode bar.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetFullscreenToggleButton(), event_generator);
  EXPECT_EQ(CaptureModeSource::kFullscreen, controller->source());
  ClickOnView(GetRegionToggleButton(), GetEventGenerator());
  ASSERT_EQ(CaptureModeSource::kRegion, controller->source());
  ASSERT_TRUE(controller->user_capture_region().Contains(
      GetCaptureModeBarView()->GetBoundsInScreen()));

  ClickOnView(GetCloseButton(), event_generator);
  EXPECT_FALSE(controller->IsActive());
}

// Tests that the magnifying glass appears while fine tuning the capture region,
// and that the cursor is hidden if the magnifying glass is present.
TEST_F(CaptureModeTest, CaptureRegionMagnifierWhenFineTuning) {
  const gfx::Vector2d kDragDelta(50, 50);
  UpdateDisplay("800x700");

  // Start Capture Mode in a region in image mode.
  StartImageRegionCapture();

  // Press down and drag to select a region. The magnifier should not be
  // visible yet.
  gfx::Rect capture_region{200, 200, 400, 400};
  SelectRegion(capture_region);
  EXPECT_EQ(std::nullopt, GetMagnifierGlassCenterPoint());

  auto check_magnifier_shows_properly = [this](const gfx::Point& origin,
                                               const gfx::Point& destination,
                                               bool should_show_magnifier) {
    // If |should_show_magnifier|, check that the magnifying glass is centered
    // on the mouse after press and during drag, and that the cursor is hidden.
    // If not |should_show_magnifier|, check that the magnifying glass never
    // shows. Should always be not visible when mouse button is released.
    auto* event_generator = GetEventGenerator();
    std::optional<gfx::Point> expected_origin =
        should_show_magnifier ? std::make_optional(origin) : std::nullopt;
    std::optional<gfx::Point> expected_destination =
        should_show_magnifier ? std::make_optional(destination) : std::nullopt;

    auto* cursor_manager = Shell::Get()->cursor_manager();
    EXPECT_TRUE(cursor_manager->IsCursorVisible());

    // Move cursor to |origin| and click.
    event_generator->set_current_screen_location(origin);
    event_generator->PressLeftButton();
    EXPECT_EQ(expected_origin, GetMagnifierGlassCenterPoint());
    EXPECT_NE(should_show_magnifier, cursor_manager->IsCursorVisible());

    // Drag to |destination| while holding left button.
    event_generator->MoveMouseTo(destination);
    EXPECT_EQ(expected_destination, GetMagnifierGlassCenterPoint());
    EXPECT_NE(should_show_magnifier, cursor_manager->IsCursorVisible());

    // Drag back to |origin| while still holding left button.
    event_generator->MoveMouseTo(origin);
    EXPECT_EQ(expected_origin, GetMagnifierGlassCenterPoint());
    EXPECT_NE(should_show_magnifier, cursor_manager->IsCursorVisible());

    // Release left button.
    event_generator->ReleaseLeftButton();
    EXPECT_EQ(std::nullopt, GetMagnifierGlassCenterPoint());
    EXPECT_TRUE(cursor_manager->IsCursorVisible());
  };

  // Drag the capture region from within the existing selected region. The
  // magnifier should not be visible at any point.
  check_magnifier_shows_properly(gfx::Point(400, 250), gfx::Point(500, 350),
                                 /*should_show_magnifier=*/false);

  // Check that each corner fine tune position shows the magnifier when
  // dragging.
  struct {
    std::string trace;
    FineTunePosition position;
  } kFineTunePositions[] = {
      {"top_left_vertex", FineTunePosition::kTopLeftVertex},
      {"top_right_vertex", FineTunePosition::kTopRightVertex},
      {"bottom_right_vertex", FineTunePosition::kBottomRightVertex},
      {"bottom_left_vertex", FineTunePosition::kBottomLeftVertex}};
  for (const auto& fine_tune_position : kFineTunePositions) {
    SCOPED_TRACE(fine_tune_position.trace);
    const gfx::Point drag_affordance_location =
        capture_mode_util::GetLocationForFineTunePosition(
            capture_region, fine_tune_position.position);
    check_magnifier_shows_properly(drag_affordance_location,
                                   drag_affordance_location + kDragDelta,
                                   /*should_show_magnifier=*/true);
  }
}

// Tests that the dimensions label properly renders for capture regions.
TEST_F(CaptureModeTest, CaptureRegionDimensionsLabelLocation) {
  UpdateDisplay("900x800");

  // Start Capture Mode in a region in image mode.
  StartImageRegionCapture();

  // Press down and don't move the mouse. Label shouldn't display for empty
  // capture regions.
  auto* generator = GetEventGenerator();
  generator->set_current_screen_location(gfx::Point(0, 0));
  generator->PressLeftButton();
  auto* controller = CaptureModeController::Get();
  EXPECT_TRUE(controller->IsActive());
  EXPECT_TRUE(controller->user_capture_region().IsEmpty());
  EXPECT_EQ(nullptr, GetDimensionsLabelWindow());
  generator->ReleaseLeftButton();

  // Press down and drag to select a large region. Verify that the dimensions
  // label is centered and that the label is below the capture region.
  gfx::Rect capture_region{100, 100, 600, 200};
  SelectRegion(capture_region, /*release_mouse=*/false);
  EXPECT_EQ(capture_region.CenterPoint().x(),
            GetDimensionsLabelWindow()->bounds().CenterPoint().x());
  EXPECT_EQ(capture_region.bottom() +
                CaptureModeSession::kSizeLabelYDistanceFromRegionDp,
            GetDimensionsLabelWindow()->bounds().y());
  generator->ReleaseLeftButton();
  EXPECT_EQ(nullptr, GetDimensionsLabelWindow());

  // Create a new capture region close to the left side of the screen such that
  // if the label was centered it would extend out of the screen.
  // The x value of the label should be the left edge of the screen (0).
  capture_region.SetRect(2, 100, 2, 100);
  SelectRegion(capture_region, /*release_mouse=*/false);
  EXPECT_EQ(0, GetDimensionsLabelWindow()->bounds().x());
  generator->ReleaseLeftButton();
  EXPECT_EQ(nullptr, GetDimensionsLabelWindow());

  // Create a new capture region close to the right side of the screen such that
  // if the label was centered it would extend out of the screen.
  // The right (x + width) of the label should be the right edge of the screen
  // (900).
  capture_region.SetRect(896, 100, 2, 100);
  SelectRegion(capture_region, /*release_mouse=*/false);
  EXPECT_EQ(900, GetDimensionsLabelWindow()->bounds().right());
  generator->ReleaseLeftButton();
  EXPECT_EQ(nullptr, GetDimensionsLabelWindow());

  // Create a new capture region close to the bottom side of the screen.
  // The label should now appear inside the capture region, just above the
  // bottom edge. It should be above the bottom of the screen as well.
  capture_region.SetRect(100, 700, 600, 100);
  SelectRegion(capture_region, /*release_mouse=*/false);
  EXPECT_EQ(800 - CaptureModeSession::kSizeLabelYDistanceFromRegionDp,
            GetDimensionsLabelWindow()->bounds().bottom());
  generator->ReleaseLeftButton();
  EXPECT_EQ(nullptr, GetDimensionsLabelWindow());
}

TEST_F(CaptureModeTest, CaptureRegionCaptureButtonLocation) {
  UpdateDisplay("900x800");

  auto* controller = StartImageRegionCapture();

  // Select a large region. Verify that the capture button widget is centered.
  SelectRegion(gfx::Rect(100, 100, 600, 600));

  views::Widget* capture_button_widget = GetCaptureModeLabelWidget();
  ASSERT_TRUE(capture_button_widget);
  aura::Window* capture_button_window =
      capture_button_widget->GetNativeWindow();
  EXPECT_EQ(gfx::Point(400, 400),
            capture_button_window->bounds().CenterPoint());

  // Drag the bottom corner so that the region is too small to fit the capture
  // button. Verify that the button is aligned horizontally and placed below the
  // region.
  auto* event_generator = GetEventGenerator();
  event_generator->DragMouseTo(gfx::Point(120, 120));
  EXPECT_EQ(gfx::Rect(100, 100, 20, 20), controller->user_capture_region());
  EXPECT_EQ(110, capture_button_window->bounds().CenterPoint().x());
  const int distance_from_region =
      CaptureModeSession::kCaptureButtonDistanceFromRegionDp;
  EXPECT_EQ(120 + distance_from_region, capture_button_window->bounds().y());

  // Click inside the region to drag the entire region to the bottom of the
  // screen. Verify that the button is aligned horizontally and placed above the
  // region.
  event_generator->set_current_screen_location(gfx::Point(110, 110));
  event_generator->DragMouseTo(gfx::Point(110, 790));
  EXPECT_EQ(gfx::Rect(100, 780, 20, 20), controller->user_capture_region());
  EXPECT_EQ(110, capture_button_window->bounds().CenterPoint().x());
  EXPECT_EQ(780 - distance_from_region,
            capture_button_window->bounds().bottom());
}

// Tests some edge cases to ensure the capture button does not intersect the
// capture bar and end up unclickable since it is stacked below the capture bar.
// Regression test for https://crbug.com/1186462.
TEST_F(CaptureModeTest, CaptureRegionCaptureButtonDoesNotIntersectCaptureBar) {
  UpdateDisplay("800x700");

  StartImageRegionCapture();

  // Create a region that would cover the capture mode bar. Add some insets to
  // ensure that the capture button could fit inside. Verify that the two
  // widgets do not overlap.
  const gfx::Rect capture_bar_bounds =
      GetCaptureModeBarWidget()->GetWindowBoundsInScreen();
  gfx::Rect region_bounds = capture_bar_bounds;
  region_bounds.Inset(-20);
  SelectRegion(region_bounds);
  EXPECT_FALSE(capture_bar_bounds.Intersects(
      GetCaptureModeLabelWidget()->GetWindowBoundsInScreen()));

  // Create a thin region above the capture mode bar. The algorithm would
  // normally place the capture label under the region, but should adjust to
  // avoid intersecting.
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(gfx::Point());
  event_generator->ClickLeftButton();
  const int capture_bar_midpoint_x = capture_bar_bounds.CenterPoint().x();
  SelectRegion(
      gfx::Rect(capture_bar_midpoint_x, capture_bar_bounds.y() - 10, 20, 10));
  EXPECT_FALSE(capture_bar_bounds.Intersects(
      GetCaptureModeLabelWidget()->GetWindowBoundsInScreen()));

  // Create a thin region below the capture mode bar which reaches the bottom of
  // the display. The algorithm would  normally place the capture label above
  // the region, but should adjust to avoid intersecting.
  event_generator->set_current_screen_location(gfx::Point());
  event_generator->ClickLeftButton();
  SelectRegion(gfx::Rect(capture_bar_midpoint_x, capture_bar_bounds.bottom(),
                         20, 700 - capture_bar_bounds.bottom()));
  EXPECT_FALSE(capture_bar_bounds.Intersects(
      GetCaptureModeLabelWidget()->GetWindowBoundsInScreen()));

  // Create a thin region that is vertical as tall as the display, and at the
  // left edge of the display. The capture label button should be right of the
  // region.
  event_generator->set_current_screen_location(gfx::Point());
  event_generator->ClickLeftButton();
  SelectRegion(gfx::Rect(20, 700));
  EXPECT_GT(GetCaptureModeLabelWidget()->GetWindowBoundsInScreen().x(), 20);
}

// Tests that pressing on the capture bar and releasing the press outside of the
// capture bar, the capture region could still be draggable and set. Regression
// test for https://crbug.com/1325028.
TEST_F(CaptureModeTest, SetCaptureRegionAfterPressOnCaptureBar) {
  UpdateDisplay("800x600");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* settings_button = GetSettingsButton();

  // Press on the settings button without release.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      settings_button->GetBoundsInScreen().CenterPoint());
  event_generator->PressLeftButton();
  // Move mouse to the outside of the capture bar and then release the press.
  event_generator->MoveMouseTo({300, 300});
  event_generator->ReleaseLeftButton();

  // Set the capture region, and verify it's set successfully.
  const gfx::Rect region_bounds(100, 100, 200, 200);
  SelectRegion(region_bounds);
  EXPECT_EQ(controller->user_capture_region(), region_bounds);
}

TEST_F(CaptureModeTest, WindowCapture) {
  // Create 2 windows that overlap with each other.
  const gfx::Rect bounds1(0, 0, 200, 200);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1));
  const gfx::Rect bounds2(150, 150, 200, 200);
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds2));

  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kWindow);
  controller->SetType(CaptureModeType::kImage);
  controller->Start(CaptureModeEntryType::kAccelTakeWindowScreenshot);
  EXPECT_TRUE(controller->IsActive());

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window1.get());
  auto* capture_mode_session = controller->capture_mode_session();
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());
  event_generator->MoveMouseToCenterOf(window2.get());
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window2.get());

  // Now move the mouse to the overlapped area.
  event_generator->MoveMouseTo(gfx::Point(175, 175));
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window2.get());
  // Close the current selected window should automatically focus to next one.
  window2.reset();
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());
  // Open another one on top also change the selected window.
  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds2));
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window3.get());
  // Minimize the window should also automatically change the selected window.
  WindowState::Get(window3.get())->Minimize();
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());

  // Stop the capture session to avoid CaptureModeSession from receiving more
  // events during test tearing down.
  controller->Stop();
}

TEST_F(CaptureModeTest, WindowCaptureConfineBoundsDoNotOverlapWindowCaption) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(200, 200)));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  GetEventGenerator()->MoveMouseToCenterOf(window.get());
  auto* capture_mode_session = controller->capture_mode_session();
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window.get());

  auto* frame_header = capture_mode_util::GetWindowFrameHeader(window.get());
  auto* caption_button_container = frame_header->caption_button_container();

  // While the session is still active, the calculated confine bounds should not
  // overlap with the frame caption.
  EXPECT_FALSE(controller->GetCaptureSurfaceConfineBounds().Intersects(
      caption_button_container->bounds()));

  // Start recording and expect that the confine bounds calculated during
  // recording still do not overlap with the frame caption.
  StartVideoRecordingImmediately();
  WaitForRecordingToStart();
  EXPECT_FALSE(controller->GetCaptureSurfaceConfineBounds().Intersects(
      caption_button_container->bounds()));
}

// Tests that the capture bar is located on the root with the cursor when
// starting capture mode.
TEST_F(CaptureModeTest, MultiDisplayCaptureBarInitialLocation) {
  UpdateDisplay("800x700,801+0-800x700");

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(1000, 500));

  auto* controller = StartImageRegionCapture();
  EXPECT_TRUE(gfx::Rect(801, 0, 800, 800)
                  .Contains(GetCaptureModeBarView()->GetBoundsInScreen()));
  controller->Stop();

  event_generator->MoveMouseTo(gfx::Point(100, 500));
  StartImageRegionCapture();
  EXPECT_TRUE(gfx::Rect(800, 800).Contains(
      GetCaptureModeBarView()->GetBoundsInScreen()));
}

// Tests behavior of a capture mode session if the active display is removed.
TEST_F(CaptureModeTest, DisplayRemoval) {
  UpdateDisplay("1200x700,1201+0-800x700");

  // Start capture mode on the secondary display.
  GetEventGenerator()->MoveMouseTo(gfx::Point(1300, 500));
  auto* controller = StartImageRegionCapture();
  auto* session = controller->capture_mode_session();
  EXPECT_TRUE(gfx::Rect(1201, 0, 800, 700)
                  .Contains(GetCaptureModeBarView()->GetBoundsInScreen()));
  ASSERT_EQ(Shell::GetAllRootWindows()[1], session->current_root());

  RemoveSecondaryDisplay();

  // Tests that the capture mode bar is now on the primary display.
  const gfx::Rect bar_bounds_in_screen =
      GetCaptureModeBarView()->GetBoundsInScreen();
  EXPECT_TRUE(gfx::Rect(1200, 700).Contains(bar_bounds_in_screen));
  ASSERT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

  // Tests that the capture mode bar is centered on the primary display.
  // Regression test for http://b/303094552.
  EXPECT_EQ(600, bar_bounds_in_screen.CenterPoint().x());
}

// Tests behavior of a capture mode session if the active display is removed
// and countdown running.
TEST_F(CaptureModeTest, DisplayRemovalWithCountdownVisible) {
  UpdateDisplay("800x700,801+0-800x700");

  // Start capture mode on the secondary display.
  auto recorded_window = CreateTestWindow(gfx::Rect(1000, 200, 400, 400));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  GetEventGenerator()->MoveMouseToCenterOf(recorded_window.get());

  auto* session = controller->capture_mode_session();

  RemoveSecondaryDisplay();

  ASSERT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

  // Test passes if no crash.
}

// Tests behavior of a capture mode session if the active display is removed,
// countdown running, fullscreen window, and in overview mode.
TEST_F(CaptureModeTest,
       DisplayRemovalWithCountdownVisibleFullscreenWindowAndInOverview) {
  UpdateDisplay("800x700,801+0-800x700");

  // Start capture mode on the secondary display.
  auto recorded_window = CreateTestWindow(gfx::Rect(1000, 200, 400, 400));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  GetEventGenerator()->MoveMouseToCenterOf(recorded_window.get());
  // Make the window fullscreen. This is important as the corner case is
  // moving a fullscreen window triggers the shelf to occur, which changes
  // display metrics.
  recorded_window->SetProperty(aura::client::kShowStateKey,
                               ui::SHOW_STATE_FULLSCREEN);

  auto* session = controller->capture_mode_session();

  EnterOverview();
  RemoveSecondaryDisplay();

  ASSERT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

  // Test passes if no crash.
}

// Tests that using fullscreen or window source, moving the mouse across
// displays will change the root window of the capture session.
TEST_F(CaptureModeTest, MultiDisplayFullscreenOrWindowSourceRootWindow) {
  UpdateDisplay("800x700,801+0-800x700");
  ASSERT_EQ(2u, Shell::GetAllRootWindows().size());

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(100, 500));

  for (auto source :
       {CaptureModeSource::kFullscreen, CaptureModeSource::kWindow}) {
    SCOPED_TRACE(source == CaptureModeSource::kFullscreen ? "Fullscreen source"
                                                          : "Window source");

    auto* controller = StartCaptureSession(source, CaptureModeType::kImage);
    auto* session = controller->capture_mode_session();
    EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

    event_generator->MoveMouseTo(gfx::Point(1000, 500));
    EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root());

    event_generator->MoveMouseTo(gfx::Point(100, 500));
    EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

    controller->Stop();
  }
}

// Tests that in region mode, moving the mouse across displays will not change
// the root window of the capture session, but clicking on a new display will.
TEST_F(CaptureModeTest, MultiDisplayRegionSourceRootWindow) {
  UpdateDisplay("800x700,801+0-800x700");
  ASSERT_EQ(2u, Shell::GetAllRootWindows().size());

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(100, 500));

  auto* controller = StartImageRegionCapture();
  auto* session = controller->capture_mode_session();
  EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

  // Tests that moving the mouse to the secondary display does not change the
  // root.
  event_generator->MoveMouseTo(gfx::Point(1000, 500));
  EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

  // Tests that pressing the mouse changes the root. The capture bar stays on
  // the primary display until the mouse is released.
  event_generator->PressLeftButton();
  EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root());
  EXPECT_TRUE(gfx::Rect(800, 800).Contains(
      GetCaptureModeBarView()->GetBoundsInScreen()));

  event_generator->ReleaseLeftButton();
  EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root());
  EXPECT_TRUE(gfx::Rect(801, 0, 800, 800)
                  .Contains(GetCaptureModeBarView()->GetBoundsInScreen()));
}

// Tests that using touch on multi display setups works as intended. Regression
// test for https://crbug.com/1159512.
TEST_F(CaptureModeTest, MultiDisplayTouch) {
  UpdateDisplay("800x700,801+0-800x700");
  ASSERT_EQ(2u, Shell::GetAllRootWindows().size());

  auto* controller = StartImageRegionCapture();
  auto* session = controller->capture_mode_session();
  ASSERT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

  // Touch and move your finger on the secondary display. We should switch roots
  // and the region size should be as expected.
  auto* event_generator = GetEventGenerator();
  event_generator->PressTouch(gfx::Point(1000, 200));
  event_generator->MoveTouch(gfx::Point(1200, 400));
  event_generator->ReleaseTouch();
  EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root());
  EXPECT_EQ(gfx::Size(200, 200), controller->user_capture_region().size());
}

TEST_F(CaptureModeTest, RegionCursorStates) {
  UpdateDisplay("800x700,801+0-800x700");

  auto* cursor_manager = Shell::Get()->cursor_manager();
  auto* event_generator = GetEventGenerator();

  struct {
    std::string scoped_trace;
    gfx::Rect display_rect;
    gfx::Point point;
    gfx::Rect capture_region;
  } kRegionTestCases[] = {
      {"primary_display", gfx::Rect(0, 0, 800, 700), gfx::Point(250, 250),
       gfx::Rect(200, 200, 200, 200)},
      {"external_display", gfx::Rect(801, 0, 800, 700), gfx::Point(1050, 250),
       gfx::Rect(1000, 200, 200, 200)},
  };

  for (auto test_case : kRegionTestCases) {
    SCOPED_TRACE(test_case.scoped_trace);
    event_generator->MoveMouseTo(test_case.point);
    const CursorType original_cursor_type = cursor_manager->GetCursor().type();
    EXPECT_FALSE(cursor_manager->IsCursorLocked());
    auto* controller = StartImageRegionCapture();
    EXPECT_TRUE(test_case.display_rect.Contains(
        GetCaptureModeBarView()->GetBoundsInScreen()));
    auto outside_point = test_case.capture_region.origin();
    outside_point.Offset(-10, -10);
    // Clear the previous region if any.
    event_generator->MoveMouseTo(outside_point);
    event_generator->ClickLeftButton();
    EXPECT_TRUE(cursor_manager->IsCursorVisible());
    EXPECT_EQ(CursorType::kCell, cursor_manager->GetCursor().type());
    EXPECT_TRUE(cursor_manager->IsCursorLocked());

    // Makes sure that the cursor is updated when the user releases the region
    // select and is still hovering in the same location.
    SelectRegion(test_case.capture_region);
    EXPECT_EQ(CursorType::kSouthEastResize, cursor_manager->GetCursor().type());

    // Verify that all of the `FineTunePosition` locations have the correct
    // cursor when hovered over both in primary display and external display.
    event_generator->MoveMouseTo(test_case.capture_region.origin());
    EXPECT_EQ(CursorType::kNorthWestResize, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.top_center());
    EXPECT_EQ(CursorType::kNorthSouthResize,
              cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.top_right());
    EXPECT_EQ(CursorType::kNorthEastResize, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.right_center());
    EXPECT_EQ(CursorType::kEastWestResize, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.bottom_right());
    EXPECT_EQ(CursorType::kSouthEastResize, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.bottom_center());
    EXPECT_EQ(CursorType::kNorthSouthResize,
              cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.bottom_left());
    EXPECT_EQ(CursorType::kSouthWestResize, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.left_center());
    EXPECT_EQ(CursorType::kEastWestResize, cursor_manager->GetCursor().type());

    // Tests that within the bounds of the selected region, the cursor is a hand
    // when hovering over the capture button, otherwise it is a
    // multi-directional move cursor.
    event_generator->MoveMouseTo(test_case.point);
    EXPECT_EQ(CursorType::kMove, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(test_case.capture_region.CenterPoint());
    EXPECT_EQ(CursorType::kHand, cursor_manager->GetCursor().type());

    // Tests that the cursor changes to a cell type when hovering over the
    // unselected region.
    event_generator->MoveMouseTo(outside_point);
    EXPECT_EQ(CursorType::kCell, cursor_manager->GetCursor().type());

    // Check that cursor is unlocked when changing sources, and that the cursor
    // changes to a pointer when hovering over the capture mode bar.
    event_generator->MoveMouseTo(
        GetRegionToggleButton()->GetBoundsInScreen().CenterPoint());
    EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(
        GetWindowToggleButton()->GetBoundsInScreen().CenterPoint());
    EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());
    event_generator->ClickLeftButton();
    ASSERT_EQ(CaptureModeSource::kWindow, controller->source());

    // The event on the capture bar to change capture source will still keep the
    // cursor locked.
    EXPECT_TRUE(cursor_manager->IsCursorLocked());
    EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());

    // Tests that on changing back to region capture mode, the cursor becomes
    // locked, and is still a pointer type over the bar, whilst a cell cursor
    // otherwise (not over the selected region).
    event_generator->MoveMouseTo(
        GetRegionToggleButton()->GetBoundsInScreen().CenterPoint());
    event_generator->ClickLeftButton();
    EXPECT_TRUE(cursor_manager->IsCursorLocked());
    EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());

    // Tests that clicking on the button again doesn't change the cursor.
    event_generator->ClickLeftButton();
    EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());
    event_generator->MoveMouseTo(outside_point);
    EXPECT_EQ(CursorType::kCell, cursor_manager->GetCursor().type());

    // Tests that when exiting capture mode that the cursor is restored to its
    // original state.
    controller->Stop();
    EXPECT_FALSE(controller->IsActive());
    EXPECT_FALSE(cursor_manager->IsCursorLocked());
    EXPECT_EQ(original_cursor_type, cursor_manager->GetCursor().type());
  }

  // Tests the cursor state in tablet mode.
  auto* controller = StartImageRegionCapture();

  // Enter tablet mode, the cursor should be hidden.
  SwitchToTabletMode();
  EXPECT_FALSE(cursor_manager->IsCursorVisible());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());

  // Move mouse but it should still be invisible.
  event_generator->MoveMouseTo(gfx::Point(100, 100));
  EXPECT_FALSE(cursor_manager->IsCursorVisible());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());

  // Return to clamshell mode, mouse should appear again.
  LeaveTabletMode();
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kCell, cursor_manager->GetCursor().type());
  controller->Stop();
}

// Regression testing for https://crbug.com/1334824.
TEST_F(CaptureModeTest, CursorShouldNotChangeWhileAdjustingRegion) {
  UpdateDisplay("800x600");

  auto* cursor_manager = Shell::Get()->cursor_manager();
  auto* event_generator = GetEventGenerator();

  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  StartImageRegionCapture();
  event_generator->MoveMouseTo(gfx::Point(200, 200));
  EXPECT_EQ(CursorType::kCell, cursor_manager->GetCursor().type());
  event_generator->PressLeftButton();
  event_generator->MoveMouseTo(gfx::Point(300, 300));
  EXPECT_EQ(CursorType::kSouthEastResize, cursor_manager->GetCursor().type());

  // Drag the region by moving the cursor to the center point of the capture bar
  // and expect that it doesn't change back to a pointer.
  const auto capture_bar_center =
      GetCaptureModeBarView()->GetBoundsInScreen().CenterPoint();
  event_generator->MoveMouseTo(capture_bar_center);
  EXPECT_EQ(CursorType::kSouthEastResize, cursor_manager->GetCursor().type());
}

TEST_F(CaptureModeTest, FullscreenCursorStates) {
  auto* cursor_manager = Shell::Get()->cursor_manager();
  CursorType original_cursor_type = cursor_manager->GetCursor().type();
  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  EXPECT_EQ(CursorType::kPointer, original_cursor_type);

  auto* event_generator = GetEventGenerator();
  CaptureModeController* controller = StartCaptureSession(
      CaptureModeSource::kFullscreen, CaptureModeType::kImage);
  EXPECT_EQ(controller->type(), CaptureModeType::kImage);
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  event_generator->MoveMouseTo(gfx::Point(175, 175));
  EXPECT_TRUE(cursor_manager->IsCursorVisible());

  // Use image capture icon as the mouse cursor icon in image capture mode.
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));

  // Move the mouse over to capture label widget won't change the cursor since
  // it's a label not a label button.
  event_generator->MoveMouseTo(test_api.GetCaptureLabelWidget()
                                   ->GetWindowBoundsInScreen()
                                   .CenterPoint());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));

  // Use pointer mouse if the event is on the capture bar.
  ClickOnView(GetVideoToggleButton(), event_generator);
  EXPECT_EQ(controller->type(), CaptureModeType::kVideo);
  EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());

  // Use video record icon as the mouse cursor icon in video recording mode.
  event_generator->MoveMouseTo(gfx::Point(175, 175));
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kVideo));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());

  // Enter tablet mode, the cursor should be hidden.
  // To avoid flaky failures due to mouse devices blocking entering tablet mode,
  // we detach all mouse devices. This shouldn't affect testing the cursor
  // status.
  SwitchToTabletMode();
  EXPECT_FALSE(cursor_manager->IsCursorVisible());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());

  // Exit tablet mode, the cursor should appear again.
  LeaveTabletMode();
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kVideo));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());

  // Stop capture mode, the cursor should be restored to its original state.
  controller->Stop();
  EXPECT_FALSE(controller->IsActive());
  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  EXPECT_EQ(original_cursor_type, cursor_manager->GetCursor().type());

  // Test that if we're in tablet mode for dev purpose, the cursor should still
  // be visible.
  Shell::Get()->tablet_mode_controller()->SetEnabledForDev(true);
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kImage);
  EXPECT_EQ(controller->type(), CaptureModeType::kImage);
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  event_generator->MoveMouseTo(gfx::Point(175, 175));
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
}

TEST_F(CaptureModeTest, WindowCursorStates) {
  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(200, 200)));

  auto* cursor_manager = Shell::Get()->cursor_manager();
  CursorType original_cursor_type = cursor_manager->GetCursor().type();
  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  EXPECT_EQ(CursorType::kPointer, original_cursor_type);

  auto* event_generator = GetEventGenerator();
  CaptureModeController* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  EXPECT_EQ(controller->type(), CaptureModeType::kImage);

  // If the mouse is above the window, use the image capture icon.
  event_generator->MoveMouseTo(gfx::Point(150, 150));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));

  // If the mouse is not above the window, use a pointer.
  event_generator->MoveMouseTo(gfx::Point(300, 300));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());

  // Use pointer mouse if the event is on the capture bar.
  ClickOnView(GetVideoToggleButton(), event_generator);
  EXPECT_EQ(controller->type(), CaptureModeType::kVideo);
  EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());

  // Use video record icon as the mouse cursor icon in video recording mode.
  event_generator->MoveMouseTo(gfx::Point(150, 150));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kVideo));

  // If the mouse is not above the window, use the original mouse cursor.
  event_generator->MoveMouseTo(gfx::Point(300, 300));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());

  // Move above the window again, the cursor should change back to the video
  // record icon.
  event_generator->MoveMouseTo(gfx::Point(150, 150));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kVideo));

  // Enter tablet mode, the cursor should be hidden.
  // To avoid flaky failures due to mouse devices blocking entering tablet mode,
  // we detach all mouse devices. This shouldn't affect testing the cursor
  // status.
  SwitchToTabletMode();
  EXPECT_FALSE(cursor_manager->IsCursorVisible());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());

  // Exit tablet mode, the cursor should appear again.
  LeaveTabletMode();
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kVideo));
  EXPECT_TRUE(cursor_manager->IsCursorLocked());

  // Stop capture mode, the cursor should be restored to its original state.
  controller->Stop();
  EXPECT_FALSE(controller->IsActive());
  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  EXPECT_EQ(original_cursor_type, cursor_manager->GetCursor().type());
}

// Tests that nothing crashes when windows are destroyed while being observed.
TEST_F(CaptureModeTest, WindowDestruction) {
  // Create 2 windows that overlap with each other.
  const gfx::Rect bounds1(0, 0, 200, 200);
  const gfx::Rect bounds2(150, 150, 200, 200);
  const gfx::Rect bounds3(50, 50, 200, 200);
  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1));
  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds2));

  auto* cursor_manager = Shell::Get()->cursor_manager();
  CursorType original_cursor_type = cursor_manager->GetCursor().type();
  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  EXPECT_EQ(CursorType::kPointer, original_cursor_type);

  // Start capture session with Image type, so we have a custom cursor.
  auto* event_generator = GetEventGenerator();
  CaptureModeController* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  EXPECT_EQ(controller->type(), CaptureModeType::kImage);

  // If the mouse is above the window, use the image capture icon.
  event_generator->MoveMouseToCenterOf(window2.get());
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  auto* capture_mode_session = controller->capture_mode_session();
  CaptureModeSessionTestApi test_api(capture_mode_session);
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));

  // Destroy the window while hovering. There is no window underneath, so it
  // should revert back to a pointer.
  window2.reset();
  EXPECT_TRUE(cursor_manager->IsCursorLocked());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
  EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());

  // Destroy the window while mouse is in a pressed state. Cursor should revert
  // back to the original cursor.
  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds2));
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));
  event_generator->PressLeftButton();
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));
  window3.reset();
  event_generator->ReleaseLeftButton();
  EXPECT_EQ(original_cursor_type, cursor_manager->GetCursor().type());

  // When hovering over a window, if it is destroyed and there is another window
  // under the cursor location in screen, then the selected window is
  // automatically updated.
  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds3));
  event_generator->MoveMouseToCenterOf(window4.get());
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window4.get());
  window4.reset();
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));
  // Check to see it's observing window1.
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());

  // Cursor is over a window in the mouse pressed state. If the window is
  // destroyed and there is another window under the cursor, the selected window
  // is updated and the new selected window is captured.
  std::unique_ptr<aura::Window> window5(CreateTestWindow(bounds3));
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window5.get());
  event_generator->PressLeftButton();
  window5.reset();
  EXPECT_EQ(CursorType::kCustom, cursor_manager->GetCursor().type());
  EXPECT_TRUE(test_api.IsUsingCustomCursor(CaptureModeType::kImage));
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());
  event_generator->ReleaseLeftButton();
  EXPECT_FALSE(controller->IsActive());
}

TEST_F(CaptureModeTest, CursorUpdatedOnDisplayRotation) {
  UpdateDisplay("600x400");
  const int64_t display_id =
      display::Screen::GetScreen()->GetPrimaryDisplay().id();
  display::SetInternalDisplayIds({display_id});
  ScreenOrientationControllerTestApi orientation_test_api(
      Shell::Get()->screen_orientation_controller());

  auto* event_generator = GetEventGenerator();
  auto* cursor_manager = Shell::Get()->cursor_manager();
  CaptureModeController* controller = StartCaptureSession(
      CaptureModeSource::kFullscreen, CaptureModeType::kImage);
  event_generator->MoveMouseTo(gfx::Point(175, 175));
  EXPECT_TRUE(cursor_manager->IsCursorVisible());

  // Use image capture icon as the mouse cursor icon in image capture mode.
  const ui::Cursor landscape_cursor = cursor_manager->GetCursor();
  EXPECT_EQ(CursorType::kCustom, landscape_cursor.type());
  CaptureModeSessionTestApi session_test_api(
      controller->capture_mode_session());
  EXPECT_TRUE(session_test_api.IsUsingCustomCursor(CaptureModeType::kImage));

  // Rotate the screen.
  orientation_test_api.SetDisplayRotation(
      display::Display::ROTATE_270, display::Display::RotationSource::ACTIVE);
  const ui::Cursor portrait_cursor = cursor_manager->GetCursor();
  EXPECT_TRUE(session_test_api.IsUsingCustomCursor(CaptureModeType::kImage));
  EXPECT_NE(landscape_cursor, portrait_cursor);
}

// Tests that in Region mode, cursor compositing is used instead of the system
// cursor when the cursor is being dragged.
TEST_F(CaptureModeTest, RegionDragCursorCompositing) {
  auto* event_generator = GetEventGenerator();
  auto* session = StartImageRegionCapture()->capture_mode_session();
  auto* cursor_manager = Shell::Get()->cursor_manager();

  // Initially cursor should be visible and cursor compositing is not enabled.
  EXPECT_FALSE(session->is_drag_in_progress());
  EXPECT_FALSE(IsCursorCompositingEnabled());
  EXPECT_TRUE(cursor_manager->IsCursorVisible());

  const gfx::Rect target_region(gfx::Rect(200, 200, 200, 200));

  // For each start and end point try dragging and verify that cursor
  // compositing is functioning as expected.
  struct {
    std::string trace;
    gfx::Point start_point;
    gfx::Point end_point;
  } kDragCases[] = {
      {"initial_region", target_region.origin(), target_region.bottom_right()},
      {"edge_resize", target_region.right_center(),
       gfx::Point(target_region.right_center() + gfx::Vector2d(50, 0))},
      {"corner_resize", target_region.origin(), gfx::Point(175, 175)},
      {"move", gfx::Point(250, 250), gfx::Point(300, 300)},
  };

  for (auto test_case : kDragCases) {
    SCOPED_TRACE(test_case.trace);

    event_generator->MoveMouseTo(test_case.start_point);
    event_generator->PressLeftButton();
    EXPECT_TRUE(session->is_drag_in_progress());
    EXPECT_TRUE(IsCursorCompositingEnabled());

    event_generator->MoveMouseTo(test_case.end_point);
    EXPECT_TRUE(session->is_drag_in_progress());
    EXPECT_TRUE(IsCursorCompositingEnabled());

    event_generator->ReleaseLeftButton();
    EXPECT_FALSE(session->is_drag_in_progress());
    EXPECT_FALSE(IsCursorCompositingEnabled());
  }
}

// Test that during countdown, capture mode session should not handle any
// incoming input events.
TEST_F(CaptureModeTest, DoNotHandleEventDuringCountDown) {
  // We need a non-zero duration to avoid infinite loop on countdown.
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Create 2 windows that overlap with each other.
  std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(200, 200)));
  std::unique_ptr<aura::Window> window2(
      CreateTestWindow(gfx::Rect(150, 150, 200, 200)));

  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kWindow);
  controller->SetType(CaptureModeType::kVideo);
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(controller->IsActive());

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window1.get());
  auto* capture_mode_session = controller->capture_mode_session();
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());

  // Start video recording. Countdown should start at this moment.
  event_generator->ClickLeftButton();

  // Now move the mouse onto the other window, we should not change the captured
  // window during countdown.
  event_generator->MoveMouseToCenterOf(window2.get());
  EXPECT_EQ(capture_mode_session->GetSelectedWindow(), window1.get());
  EXPECT_NE(capture_mode_session->GetSelectedWindow(), window2.get());

  WaitForRecordingToStart();
}

// Test that during countdown, window changes or crashes are handled.
TEST_F(CaptureModeTest, WindowChangesDuringCountdown) {
  // We need a non-zero duration to avoid infinite loop on countdown.
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  std::unique_ptr<aura::Window> window;

  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kWindow);
  controller->SetType(CaptureModeType::kVideo);

  auto start_countdown = [this, &window, controller]() {
    window = CreateTestWindow(gfx::Rect(200, 200));
    controller->Start(CaptureModeEntryType::kQuickSettings);

    auto* event_generator = GetEventGenerator();
    event_generator->MoveMouseToCenterOf(window.get());
    event_generator->ClickLeftButton();

    EXPECT_TRUE(controller->IsActive());
    EXPECT_FALSE(controller->is_recording_in_progress());
  };

  // Destroying or minimizing the observed window terminates the countdown and
  // exits capture mode.
  start_countdown();
  window.reset();
  EXPECT_FALSE(controller->IsActive());

  start_countdown();
  WindowState::Get(window.get())->Minimize();
  EXPECT_FALSE(controller->IsActive());

  // Activation changes (such as opening overview) should not terminate the
  // countdown.
  start_countdown();
  EnterOverview();
  EXPECT_TRUE(controller->IsActive());
  EXPECT_FALSE(controller->is_recording_in_progress());

  // Wait for countdown to finish and check that recording starts.
  WaitForRecordingToStart();
  EXPECT_FALSE(controller->IsActive());
  EXPECT_TRUE(controller->is_recording_in_progress());
}

// Verifies that the video notification will show the same thumbnail image as
// sent by recording service.
TEST_F(CaptureModeTest, VideoNotificationThumbnail) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());
  CaptureModeTestApi().FlushRecordingServiceForTesting();

  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());

  // Request and wait for a video frame so that the recording service can use it
  // to create a video thumbnail.
  test_delegate->RequestAndWaitForVideoFrame();
  SkBitmap service_thumbnail =
      gfx::Image(test_delegate->GetVideoThumbnail()).AsBitmap();
  EXPECT_FALSE(service_thumbnail.drawsNothing());

  CaptureNotificationWaiter waiter;
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  EXPECT_FALSE(controller->is_recording_in_progress());
  waiter.Wait();

  // Verify that the service's thumbnail is the same image shown in the
  // notification shown when recording ends.
  const message_center::Notification* notification = GetPreviewNotification();
  EXPECT_TRUE(notification);
  EXPECT_FALSE(notification->image().IsEmpty());
  const SkBitmap notification_thumbnail = notification->image().AsBitmap();
  EXPECT_TRUE(
      gfx::test::AreBitmapsEqual(notification_thumbnail, service_thumbnail));
}

TEST_F(CaptureModeTest, LowDriveFsSpace) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  const base::FilePath drive_fs_folder = CreateFolderOnDriveFS("test");
  controller->SetCustomCaptureFolder(drive_fs_folder);
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());

  // Simulate low DriveFS free space by setting it to e.g. 200 MB.
  test_delegate->set_fake_drive_fs_free_bytes(200 * 1024 * 1024);

  base::HistogramTester histogram_tester;
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());
  CaptureModeTestApi().FlushRecordingServiceForTesting();
  test_delegate->RequestAndWaitForVideoFrame();

  // Recording should end immediately due to a low Drive FS free space.
  WaitForCaptureFileToBeSaved();
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kLowDriveFsQuota, 1);
}

TEST_F(CaptureModeTest, WindowRecordingCaptureId) {
  auto window = CreateTestWindow(gfx::Rect(200, 200));
  StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window.get());
  auto* controller = CaptureModeController::Get();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // The window should have a valid capture ID.
  EXPECT_TRUE(window->subtree_capture_id().is_valid());

  // Once recording ends, the window should no longer be marked as capturable.
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  EXPECT_FALSE(controller->is_recording_in_progress());
  EXPECT_FALSE(window->subtree_capture_id().is_valid());
}

TEST_F(CaptureModeTest, ClosingDimmedWidgetAboveRecordedWindow) {
  views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
  auto* window = widget->GetNativeWindow();
  auto recorded_window = CreateTestWindow(gfx::Rect(200, 200));

  auto* controller = StartSessionAndRecordWindow(recorded_window.get());
  EXPECT_TRUE(controller->is_recording_in_progress());
  auto* recording_watcher = controller->video_recording_watcher_for_testing();

  // Activate the window so that it becomes on top of the recorded window, and
  // expect it gets dimmed.
  wm::ActivateWindow(window);
  EXPECT_TRUE(recording_watcher->IsWindowDimmedForTesting(window));

  // Close the widget, this should not lead to any use-after-free. See
  // https://crbug.com/1273197.
  widget->Close();
}

TEST_F(CaptureModeTest, DimmingOfUnRecordedWindows) {
  auto win1 = CreateTestWindow(gfx::Rect(200, 200));
  auto win2 = CreateTestWindow(gfx::Rect(200, 200));
  auto recorded_window = CreateTestWindow(gfx::Rect(200, 200));

  auto* controller = StartSessionAndRecordWindow(recorded_window.get());
  auto* recording_watcher = controller->video_recording_watcher_for_testing();
  auto* shield_layer = recording_watcher->layer();
  // Since the recorded window is the top most, no windows should be
  // individually dimmed.
  EXPECT_TRUE(recording_watcher->should_paint_layer());
  EXPECT_TRUE(IsLayerStackedRightBelow(shield_layer, recorded_window->layer()));
  EXPECT_FALSE(
      recording_watcher->IsWindowDimmedForTesting(recorded_window.get()));
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win1.get()));
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win2.get()));

  // Activating |win1| brings it to the front of the shield, so it should be
  // dimmed separately.
  wm::ActivateWindow(win1.get());
  EXPECT_TRUE(recording_watcher->should_paint_layer());
  EXPECT_TRUE(IsLayerStackedRightBelow(shield_layer, recorded_window->layer()));
  EXPECT_FALSE(
      recording_watcher->IsWindowDimmedForTesting(recorded_window.get()));
  EXPECT_TRUE(recording_watcher->IsWindowDimmedForTesting(win1.get()));
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win2.get()));
  // Similarly for |win2|.
  wm::ActivateWindow(win2.get());
  EXPECT_TRUE(recording_watcher->should_paint_layer());
  EXPECT_TRUE(IsLayerStackedRightBelow(shield_layer, recorded_window->layer()));
  EXPECT_FALSE(
      recording_watcher->IsWindowDimmedForTesting(recorded_window.get()));
  EXPECT_TRUE(recording_watcher->IsWindowDimmedForTesting(win1.get()));
  EXPECT_TRUE(recording_watcher->IsWindowDimmedForTesting(win2.get()));

  // Minimizing the recorded window should stop painting the shield, and the
  // dimmers should be removed.
  WindowState::Get(recorded_window.get())->Minimize();
  EXPECT_FALSE(recording_watcher->should_paint_layer());
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win1.get()));
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win2.get()));

  // Activating the recorded window again unminimizes the window, which will
  // reenable painting the shield.
  wm::ActivateWindow(recorded_window.get());
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win1.get()));
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win2.get()));
  EXPECT_FALSE(WindowState::Get(recorded_window.get())->IsMinimized());
  EXPECT_TRUE(recording_watcher->should_paint_layer());

  // Destroying a dimmed window is correctly tracked.
  wm::ActivateWindow(win2.get());
  EXPECT_TRUE(recording_watcher->IsWindowDimmedForTesting(win2.get()));
  win2.reset();
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win2.get()));
}

TEST_F(CaptureModeTest, DimmingWithDesks) {
  auto recorded_window = CreateAppWindow(gfx::Rect(250, 100));
  auto* controller = StartSessionAndRecordWindow(recorded_window.get());
  auto* recording_watcher = controller->video_recording_watcher_for_testing();
  EXPECT_TRUE(recording_watcher->should_paint_layer());

  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  Desk* desk_2 = desks_controller->desks()[1].get();
  ActivateDesk(desk_2);

  // A window on a different desk than that of the recorded window should not be
  // dimmed.
  auto win1 = CreateAppWindow(gfx::Rect(200, 200));
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win1.get()));

  // However, moving it to the desk of the recorded window should give it a
  // dimmer, since it's a more recently-used window (i.e. above the recorded
  // window).
  Desk* desk_1 = desks_controller->desks()[0].get();
  desks_controller->MoveWindowFromActiveDeskTo(
      win1.get(), desk_1, win1->GetRootWindow(),
      DesksMoveWindowFromActiveDeskSource::kShortcut);
  EXPECT_TRUE(recording_watcher->IsWindowDimmedForTesting(win1.get()));

  // Moving the recorded window out of the active desk should destroy the
  // dimmer.
  ActivateDesk(desk_1);
  desks_controller->MoveWindowFromActiveDeskTo(
      recorded_window.get(), desk_2, recorded_window->GetRootWindow(),
      DesksMoveWindowFromActiveDeskSource::kShortcut);
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win1.get()));
}

TEST_F(CaptureModeTest, DimmingWithDisplays) {
  UpdateDisplay("500x400,401+0-800x700");
  auto recorded_window = CreateAppWindow(gfx::Rect(250, 100));
  auto* controller = StartSessionAndRecordWindow(recorded_window.get());
  auto* recording_watcher = controller->video_recording_watcher_for_testing();
  EXPECT_TRUE(recording_watcher->should_paint_layer());

  // Create a new window on the second display. It should not be dimmed.
  auto window = CreateTestWindow(gfx::Rect(420, 10, 200, 200));
  auto roots = Shell::GetAllRootWindows();
  EXPECT_EQ(roots[1], window->GetRootWindow());
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(window.get()));

  // However when moved to the first display, it gets dimmed.
  window_util::MoveWindowToDisplay(window.get(),
                                   roots[0]->GetHost()->GetDisplayId());
  EXPECT_TRUE(recording_watcher->IsWindowDimmedForTesting(window.get()));

  // Moving the recorded window to the second display will remove the dimming of
  // |window|.
  window_util::MoveWindowToDisplay(recorded_window.get(),
                                   roots[1]->GetHost()->GetDisplayId());
  EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(window.get()));
}

TEST_F(CaptureModeTest, MultiDisplayWindowRecording) {
  UpdateDisplay("500x400,401+0-800x700");
  auto roots = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, roots.size());

  auto window = CreateTestWindow(gfx::Rect(200, 200));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window.get());
  auto* session_layer = controller->capture_mode_session()->layer();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());
  // The session layer is reused to paint the recording shield.
  auto* shield_layer =
      controller->video_recording_watcher_for_testing()->layer();
  EXPECT_EQ(session_layer, shield_layer);
  EXPECT_EQ(shield_layer->parent(), window->layer()->parent());
  EXPECT_TRUE(IsLayerStackedRightBelow(shield_layer, window->layer()));
  EXPECT_EQ(shield_layer->bounds(), roots[0]->bounds());

  // The capturer should capture from the frame sink of the first display.
  // The video size should match the window's size.
  CaptureModeTestApi test_api;
  test_api.FlushRecordingServiceForTesting();
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  EXPECT_EQ(roots[0]->GetFrameSinkId(), test_delegate->GetCurrentFrameSinkId());
  EXPECT_EQ(roots[0]->bounds().size(),
            test_delegate->GetCurrentFrameSinkSizeInPixels());
  EXPECT_EQ(window->bounds().size(), test_delegate->GetCurrentVideoSize());

  // Moving a window to a different display should be propagated to the service,
  // with the new root's frame sink ID, and the new root's size.
  window_util::MoveWindowToDisplay(window.get(),
                                   roots[1]->GetHost()->GetDisplayId());
  test_api.FlushRecordingServiceForTesting();
  ASSERT_EQ(window->GetRootWindow(), roots[1]);
  EXPECT_EQ(roots[1]->GetFrameSinkId(), test_delegate->GetCurrentFrameSinkId());
  EXPECT_EQ(roots[1]->bounds().size(),
            test_delegate->GetCurrentFrameSinkSizeInPixels());
  EXPECT_EQ(window->bounds().size(), test_delegate->GetCurrentVideoSize());

  // The shield layer should move with the window, and maintain the stacking
  // below the window's layer.
  EXPECT_EQ(shield_layer->parent(), window->layer()->parent());
  EXPECT_TRUE(IsLayerStackedRightBelow(shield_layer, window->layer()));
  EXPECT_EQ(shield_layer->bounds(), roots[1]->bounds());
}

// Flaky especially on MSan: https://crbug.com/1293188
TEST_F(CaptureModeTest, DISABLED_WindowResizing) {
  UpdateDisplay("700x600");
  auto window = CreateTestWindow(gfx::Rect(200, 200));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window.get());
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());

  CaptureModeTestApi test_api;
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(200, 200), test_delegate->GetCurrentVideoSize());
  EXPECT_EQ(gfx::Size(700, 600),
            test_delegate->GetCurrentFrameSinkSizeInPixels());

  // Multiple resize events should be throttled.
  window->SetBounds(gfx::Rect(250, 250));
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(200, 200), test_delegate->GetCurrentVideoSize());

  window->SetBounds(gfx::Rect(250, 300));
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(200, 200), test_delegate->GetCurrentVideoSize());

  window->SetBounds(gfx::Rect(300, 300));
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(200, 200), test_delegate->GetCurrentVideoSize());

  // Once throttling ends, the current size is pushed.
  auto* recording_watcher = controller->video_recording_watcher_for_testing();
  recording_watcher->SendThrottledWindowSizeChangedNowForTesting();
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(300, 300), test_delegate->GetCurrentVideoSize());
  EXPECT_EQ(gfx::Size(700, 600),
            test_delegate->GetCurrentFrameSinkSizeInPixels());

  // Maximizing a window changes its size, and is pushed to the service with
  // throttling.
  WindowState::Get(window.get())->Maximize();
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(300, 300), test_delegate->GetCurrentVideoSize());

  recording_watcher->SendThrottledWindowSizeChangedNowForTesting();
  test_api.FlushRecordingServiceForTesting();
  EXPECT_NE(gfx::Size(300, 300), test_delegate->GetCurrentVideoSize());
  EXPECT_EQ(window->bounds().size(), test_delegate->GetCurrentVideoSize());
}

TEST_F(CaptureModeTest, RotateDisplayWhileRecording) {
  UpdateDisplay("600x800");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  SelectRegion(gfx::Rect(20, 40, 100, 200));
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // Initially the frame sink size matches the un-rotated display size in DIPs,
  // but the video size matches the size of the crop region.
  CaptureModeTestApi test_api;
  test_api.FlushRecordingServiceForTesting();
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  EXPECT_EQ(gfx::Size(600, 800),
            test_delegate->GetCurrentFrameSinkSizeInPixels());
  EXPECT_EQ(gfx::Size(100, 200), test_delegate->GetCurrentVideoSize());

  // Rotate by 90 degree, the frame sink size should be updated to match that.
  // The video size should remain unaffected.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(), display::Display::ROTATE_90,
      display::Display::RotationSource::USER);
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(800, 600),
            test_delegate->GetCurrentFrameSinkSizeInPixels());
  EXPECT_EQ(gfx::Size(100, 200), test_delegate->GetCurrentVideoSize());
}

// Regression test for https://crbug.com/1331095.
// This is disabled due to flakiness: b/318349807
TEST_F(CaptureModeTest, DISABLED_CornerRegionWithScreenRotation) {
  UpdateDisplay("800x600");

  // Pick a region at the bottom right corner of the landscape screen, so that
  // when the screen is rotated to portrait, the unadjusted region becomes
  // outside the new portrait bounds.
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  SelectRegion(gfx::Rect(700, 400, 100, 200));
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  CaptureModeTestApi test_api;
  test_api.FlushRecordingServiceForTesting();
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());

  EXPECT_EQ(gfx::Size(100, 200), test_delegate->GetCurrentVideoSize());
  auto* root_window = Shell::GetPrimaryRootWindow();
  auto* recording_watcher = controller->video_recording_watcher_for_testing();
  gfx::Rect effective_region_bounds =
      recording_watcher->GetEffectivePartialRegionBounds();
  EXPECT_FALSE(effective_region_bounds.IsEmpty());
  EXPECT_TRUE(root_window->bounds().Contains(effective_region_bounds));

  // Verifies that the bounds of the visible rect of the frame is within the
  // bounds of the root window.
  auto verify_video_frame = [&](const media::VideoFrame& frame,
                                const gfx::Rect& content_rect) {
    EXPECT_TRUE(root_window->bounds().Contains(frame.visible_rect()));
  };

  test_delegate->recording_service()->RequestAndWaitForVideoFrame(
      base::BindLambdaForTesting(verify_video_frame));

  // Rotate by 90 degree, the adjusted region bounds should not be empty and
  // should remain within the bounds of the new portrait root window bounds.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(), display::Display::ROTATE_90,
      display::Display::RotationSource::USER);
  test_api.FlushRecordingServiceForTesting();
  EXPECT_EQ(gfx::Size(100, 200), test_delegate->GetCurrentVideoSize());

  effective_region_bounds =
      recording_watcher->GetEffectivePartialRegionBounds();
  EXPECT_FALSE(effective_region_bounds.IsEmpty());
  EXPECT_TRUE(root_window->bounds().Contains(effective_region_bounds));
  test_delegate->recording_service()->RequestAndWaitForVideoFrame(
      base::BindLambdaForTesting(verify_video_frame));
}

// Tests that the video frames delivered to the service for recorded windows are
// valid (i.e. they have the correct size, and suffer from no letterboxing, even
// when the window gets resized).
// This is a regression test for https://crbug.com/1214023.
//
// TODO(crbug.com/1439950): This test is flaky.
TEST_F(CaptureModeTest, DISABLED_VerifyWindowRecordingVideoFrames) {
  auto window = CreateTestWindow(gfx::Rect(100, 50, 200, 200));
  StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window.get());
  auto* controller = CaptureModeController::Get();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());
  CaptureModeTestApi test_api;
  test_api.FlushRecordingServiceForTesting();

  bool is_video_frame_valid = false;
  std::string failures;
  auto verify_video_frame = [&](const media::VideoFrame& frame,
                                const gfx::Rect& content_rect) {
    is_video_frame_valid = true;
    failures.clear();

    // Having the content positioned at (0,0) with a size that matches the
    // current window's size means that there is no letterboxing.
    if (gfx::Point() != content_rect.origin()) {
      is_video_frame_valid = false;
      failures =
          base::StringPrintf("content_rect is not at (0,0), instead at: %s\n",
                             content_rect.origin().ToString().c_str());
    }

    const gfx::Size window_size = window->bounds().size();
    if (window_size != content_rect.size()) {
      is_video_frame_valid = false;
      failures += base::StringPrintf(
          "content_rect doesn't match the window size:\n"
          "  content_rect.size(): %s\n"
          "  window_size: %s\n",
          content_rect.size().ToString().c_str(),
          window_size.ToString().c_str());
    }

    // The video frame contents should match the bounds of the video frame.
    if (frame.visible_rect() != content_rect) {
      is_video_frame_valid = false;
      failures += base::StringPrintf(
          "content_rect doesn't match the frame's visible_rect:\n"
          "  content_rect: %s\n"
          "  visible_rect: %s\n",
          content_rect.ToString().c_str(),
          frame.visible_rect().ToString().c_str());
    }

    if (frame.coded_size() != window_size) {
      is_video_frame_valid = false;
      failures += base::StringPrintf(
          "the frame's coded size doesn't match the window size:\n"
          "  frame.coded_size(): %s\n"
          "  window_size: %s\n",
          frame.coded_size().ToString().c_str(),
          window_size.ToString().c_str());
    }
  };

  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  ASSERT_TRUE(test_delegate->recording_service());
  {
    SCOPED_TRACE("Initial window size");
    test_delegate->recording_service()->RequestAndWaitForVideoFrame(
        base::BindLambdaForTesting(verify_video_frame));
    EXPECT_TRUE(is_video_frame_valid) << failures;
  }

  // Even when the window is resized and the throttled size reaches the service,
  // new video frames should still be valid.
  window->SetBounds(gfx::Rect(120, 60, 600, 500));
  auto* recording_watcher = controller->video_recording_watcher_for_testing();
  recording_watcher->SendThrottledWindowSizeChangedNowForTesting();
  test_api.FlushRecordingServiceForTesting();
  {
    SCOPED_TRACE("After window resizing");
    // A video frame is produced on the Viz side when a CopyOutputRequest is
    // fulfilled. Those CopyOutputRequests could have been placed before the
    // window's layer resize results in a new resized render pass in Viz. But
    // eventually this must happen, and a valid frame must be delivered.
    int remaining_attempts = 2;
    do {
      --remaining_attempts;
      test_delegate->recording_service()->RequestAndWaitForVideoFrame(
          base::BindLambdaForTesting(verify_video_frame));
    } while (!is_video_frame_valid && remaining_attempts);
    EXPECT_TRUE(is_video_frame_valid) << failures;
  }

  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  EXPECT_FALSE(controller->is_recording_in_progress());
}

// Tests that the focus should be on the `Settings` button after closing the
// settings menu.
TEST_F(CaptureModeTest, ReturnFocusToSettingsButtonAfterSettingsMenuIsClosed) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kImage);
  auto* capture_mode_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_mode_session->session_type(), SessionType::kReal);
  CaptureModeSessionTestApi test_api(capture_mode_session);

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* event_generator = GetEventGenerator();

  // Check the initial focus of the focus ring.
  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());

  // Tab six times, `Settings` button should be focused.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_TRUE(CaptureModeSessionFocusCycler::HighlightHelper::Get(
                  test_api.GetCaptureModeBarView()->settings_button())
                  ->has_focus());

  // Press the space key and the settings menu will be opened.
  SendKey(ui::VKEY_SPACE, event_generator, ui::EF_NONE);
  EXPECT_TRUE(test_api.GetCaptureModeSettingsView());
  EXPECT_EQ(FocusGroup::kPendingSettings, test_api.GetCurrentFocusGroup());

  // Close the settings menu, the focus ring should be on the `Settings` button.
  SendKey(ui::VKEY_ESCAPE, event_generator, ui::EF_NONE);
  EXPECT_FALSE(test_api.GetCaptureModeSettingsView());
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_TRUE(CaptureModeSessionFocusCycler::HighlightHelper::Get(
                  test_api.GetCaptureModeBarView()->settings_button())
                  ->has_focus());

  // Tab the space key to open the settings menu again and tab to focus on the
  // settings menu item.
  SendKey(ui::VKEY_SPACE, event_generator, ui::EF_NONE);
  EXPECT_TRUE(test_api.GetCaptureModeSettingsView());
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/3);
  EXPECT_EQ(FocusGroup::kSettingsMenu, test_api.GetCurrentFocusGroup());

  // Close the settings menu, the focus ring should be on the `Settings` button.
  SendKey(ui::VKEY_ESCAPE, event_generator, ui::EF_NONE);
  EXPECT_FALSE(test_api.GetCaptureModeSettingsView());
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_TRUE(CaptureModeSessionFocusCycler::HighlightHelper::Get(
                  test_api.GetCaptureModeBarView()->settings_button())
                  ->has_focus());
}

// Tests that minimized window(s) will be ignored whereas four corners occluded
// but overall partially occluded window will be focusable while tabbing through
// in `kWindow` mode.
TEST_F(CaptureModeTest, IgnoreMinimizeWindowsInKWindow) {
  // Layout of three windows: four corners of `window3` are occluded by
  // `window1` and `window2`.
  //
  //   +------+
  //   |      |       +-----------+
  //   |  1   |-------|           |
  //   |      |   3   |     2     |
  //   |      |       |           |
  //   |      |       |           |
  //   |      |-------|           |
  //   |      |       +-----------+
  //   +------+
  std::unique_ptr<aura::Window> window3 =
      CreateTestWindow(gfx::Rect(100, 45, 150, 200));
  std::unique_ptr<aura::Window> window2 =
      CreateTestWindow(gfx::Rect(150, 50, 150, 250));
  std::unique_ptr<aura::Window> window1 =
      CreateTestWindow(gfx::Rect(20, 30, 100, 300));
  std::unique_ptr<aura::Window> window4(
      CreateTestWindow(gfx::Rect(0, 0, 50, 90)));
  WindowState::Get(window4.get())->Minimize();

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* capture_mode_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_mode_session->session_type(), SessionType::kReal);
  CaptureModeSessionTestApi test_api(capture_mode_session);
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* event_generator = GetEventGenerator();

  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Tab six times, `window1` should be focused. Tab another time, `window2`
  // should be focused. Tab again, `window3` will be focused.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(window1.get(), capture_mode_session->GetSelectedWindow());

  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());

  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window3.get(), capture_mode_session->GetSelectedWindow());

  // Tab once, the `settings` button should be focused. The minimized `window4`
  // will be ignored during the tabbing process.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  controller->Stop();
}

// Tests that partially occluded window(s) will be focusable even when four
// edges are occluded by other windows while tabbing through in `kWindow` mode.
TEST_F(CaptureModeTest, PartiallyOccludedWindowIsFocusableInKWindow) {
  // Layout of five windows: four edges of `window3` is occluded by `window1`,
  // `window2`, `window4` and `window5` respectively, but the middle part is not
  // occluded.
  //        +-----------+
  //        |           |
  //   +----|     4     |
  //   |    |           |---------+
  //   |    |           |         |
  //   |    +-|-------|-+         |
  //   |  1   |   3   |     2     |
  //   |      |       |           |
  //   |    +-|-------|--+        |
  //   |    |            |--------+
  //   +----|     5      |
  //        |            |
  //        +------------+
  std::unique_ptr<aura::Window> window3 =
      CreateTestWindow(gfx::Rect(100, 45, 150, 200));
  std::unique_ptr<aura::Window> window2 =
      CreateTestWindow(gfx::Rect(150, 50, 150, 250));
  std::unique_ptr<aura::Window> window1 =
      CreateTestWindow(gfx::Rect(20, 30, 100, 300));
  std::unique_ptr<aura::Window> window4 =
      CreateTestWindow(gfx::Rect(50, 5, 150, 55));
  std::unique_ptr<aura::Window> window5 =
      CreateTestWindow(gfx::Rect(60, 225, 210, 45));

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* capture_mode_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_mode_session->session_type(), SessionType::kReal);
  CaptureModeSessionTestApi test_api(capture_mode_session);
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* event_generator = GetEventGenerator();

  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Tab six times, `window5` should be focused. Then `window4`, `window1`,
  // `window2` and `window3` will be focused after each tab.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(window5.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window4.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window1.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window3.get(), capture_mode_session->GetSelectedWindow());

  // Tab once, the `settings` button should be focused.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  controller->Stop();
}

// Tests that fully occluded window(s) will be ignored while tabbing in
// `kWindow`.
TEST_F(CaptureModeTest, IgnoreFullyOccludedWindowWhileTabbingInKWindow) {
  // Layout of six windows: `window3` is fully occluded by `window1`, `window2`,
  // `window4`, `window5` and `window6`.
  //        +-----------+
  //        |           |
  //   +----|     4     |
  //   | 1  |           |---------+
  //   |  +-----------------+     |
  //   |  |                 |     |
  //   |  |       6         |  2  |
  //   |  |                 |     |
  //   |  +-----------------+     |
  //   |    |            |--------+
  //   +----|     5      |
  //        |            |
  //        +------------+
  std::unique_ptr<aura::Window> window3 =
      CreateTestWindow(gfx::Rect(100, 45, 150, 200));
  std::unique_ptr<aura::Window> window2 =
      CreateTestWindow(gfx::Rect(150, 50, 150, 250));
  std::unique_ptr<aura::Window> window1 =
      CreateTestWindow(gfx::Rect(20, 30, 100, 300));
  std::unique_ptr<aura::Window> window4 =
      CreateTestWindow(gfx::Rect(50, 5, 150, 55));
  std::unique_ptr<aura::Window> window5 =
      CreateTestWindow(gfx::Rect(60, 225, 210, 45));
  std::unique_ptr<aura::Window> window6 =
      CreateTestWindow(gfx::Rect(30, 55, 175, 185));

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* capture_mode_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_mode_session->session_type(), SessionType::kReal);
  CaptureModeSessionTestApi test_api(capture_mode_session);
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* event_generator = GetEventGenerator();

  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Tab six times, `window6` should be focused. Then `window5`, `window4`,
  // `window1` and `window2` will be focused after each tab.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(window6.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window5.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window4.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window1.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());

  // Tab once, the `settings` button should be focused. The fully occluded
  // `window3` will be ignored during the tabbing process.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
}

// Tests that only Tab and Shift + Tab events advance/reverse focus and stop
// event propagation. Other events like Alt + Tab should still behave as
// intended.
TEST_F(CaptureModeTest, OnlyAdvanceFocusWhenTabShiftPressed) {
  auto window1 = CreateTestWindow();
  auto window2 = CreateTestWindow();

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  auto* capture_mode_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_mode_session->session_type(), SessionType::kReal);
  CaptureModeSessionTestApi test_api(capture_mode_session);
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* event_generator = GetEventGenerator();

  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Tab should advance focus forward.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());

  // Shift + Tab should advance focus backwards (reverse focus).
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN, /*count=*/5);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());

  // Non-shortcut modifiers like Caps Lock should not count towards the flags we
  // are concerned with, so Tab and Shift + Tab should still work normally.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_CAPS_LOCK_ON, /*count=*/5);
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NUM_LOCK_ON | ui::EF_SHIFT_DOWN,
          /*count=*/5);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());

  // Alt + Tab should cycle the active window, and the focus should not change.
  ASSERT_EQ(window_util::GetActiveWindow(), window2.get());
  // We need to wait synchronously until the event has been fully processed to
  // check if the activation has been changed.
  ui::test::EmulateFullKeyPressReleaseSequence(event_generator, ui::VKEY_TAB,
                                               false, false, true, false);
  EXPECT_EQ(window_util::GetActiveWindow(), window1.get());
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  ui::test::EmulateFullKeyPressReleaseSequence(event_generator, ui::VKEY_TAB,
                                               false, false, true, false);
  EXPECT_EQ(window_util::GetActiveWindow(), window2.get());
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());

  // Alt + Shift + Tab should also cycle the active window in the reverse
  // direction.
  ui::test::EmulateFullKeyPressReleaseSequence(event_generator, ui::VKEY_TAB,
                                               false, true, true, false);
  EXPECT_EQ(window_util::GetActiveWindow(), window1.get());
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());

  // Ctrl + Tab and Ctrl + Shift + Tab should not do anything.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_CONTROL_DOWN);
  EXPECT_EQ(window_util::GetActiveWindow(), window1.get());
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  SendKey(ui::VKEY_TAB, event_generator,
          ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
  EXPECT_EQ(window_util::GetActiveWindow(), window1.get());
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
}

// Tests that the capture region will be refreshed if in overview to reflect the
// bounds of the overview item for this window in `kWindow` mode.
TEST_F(CaptureModeTest, RefreshCaptureRegionInOverviewForKWindow) {
  auto window = CreateAppWindow(gfx::Rect(100, 50, 200, 200));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* session = controller->capture_mode_session();

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window.get());
  EXPECT_EQ(window.get(), session->GetSelectedWindow());

  // Start overview and verify that the capture region is refreshed correctly.
  auto* overview_controller = OverviewController::Get();
  overview_controller->StartOverview(OverviewStartAction::kTests);
  ASSERT_TRUE(overview_controller->InOverviewSession());
  auto* overview_item =
      overview_controller->overview_session()->GetOverviewItemForWindow(
          window.get());
  const auto target_bounds = overview_item->target_bounds();
  event_generator->MoveMouseTo(
      gfx::ToRoundedPoint(target_bounds.CenterPoint()));
  auto capture_region_in_overview =
      CaptureModeSessionTestApi(session).GetSelectedWindowTargetBounds();
  wm::ConvertRectToScreen(window->GetRootWindow(), &capture_region_in_overview);
  EXPECT_EQ(capture_region_in_overview, gfx::ToRoundedRect(target_bounds));
}

class CaptureModeSaveFileTest
    : public CaptureModeTest,
      public testing::WithParamInterface<CaptureModeType> {
 public:
  CaptureModeSaveFileTest() = default;
  CaptureModeSaveFileTest(
      const CaptureModeSaveFileTest& capture_mode_save_file_test) = delete;
  CaptureModeSaveFileTest& operator=(const CaptureModeSaveFileTest&) = delete;
  ~CaptureModeSaveFileTest() override = default;

  void StartCaptureSessionWithParam() {
    StartCaptureSession(CaptureModeSource::kFullscreen, GetParam());
  }

  // Based on the `CaptureModeType`, it performs the capture and then returns
  // the path of the saved image or video files.
  base::FilePath PerformCapture() {
    auto* controller = CaptureModeController::Get();
    switch (GetParam()) {
      case CaptureModeType::kImage:
        controller->PerformCapture();
        return WaitForCaptureFileToBeSaved();

      case CaptureModeType::kVideo:
        StartVideoRecordingImmediately();
        controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
        return WaitForCaptureFileToBeSaved();
    }
  }
};

// Tests that if the custom folder becomes unavailable, the captured file should
// be saved into the default folder. Otherwise, it's saved into custom folder.
TEST_P(CaptureModeSaveFileTest, SaveCapturedFileWithCustomFolder) {
  auto* controller = CaptureModeController::Get();
  const base::FilePath default_folder =
      controller->delegate_for_testing()->GetUserDefaultDownloadsFolder();
  const base::FilePath custom_folder((FILE_PATH_LITERAL("/home/tests")));
  controller->SetCustomCaptureFolder(custom_folder);

  // Make sure the current folder is the custom folder here and then perform
  // capture.
  auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);
  StartCaptureSessionWithParam();
  base::FilePath file_saved_path = PerformCapture();

  // Since `custom_folder` is not available, the captured files will be saved
  // into default folder;
  EXPECT_EQ(file_saved_path.DirName(), default_folder);

  // Now create an available custom folder and set it for custom capture folder.
  const base::FilePath available_custom_folder =
      CreateCustomFolderInUserDownloadsPath("test");
  controller->SetCustomCaptureFolder(available_custom_folder);

  capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);
  StartCaptureSessionWithParam();
  file_saved_path = PerformCapture();

  // Since `available_custom_folder` is now available, the captured files will
  // be saved into the custom folder;
  EXPECT_EQ(file_saved_path.DirName(), available_custom_folder);
}

TEST_P(CaptureModeSaveFileTest, CaptureModeSaveToLocationMetric) {
  constexpr char kHistogramBase[] = "SaveLocation";
  const std::string histogram_name = BuildHistogramName(
      kHistogramBase, /*behavior=*/nullptr, /*append_ui_mode_suffix=*/true);
  base::HistogramTester histogram_tester;
  auto* controller = CaptureModeController::Get();
  auto* test_delegate = controller->delegate_for_testing();

  // Initialize four different save-to locations for screen capture that
  // includes default downloads folder, local customized folder, root drive and
  // a specific folder on drive.
  const auto downloads_folder = test_delegate->GetUserDefaultDownloadsFolder();
  const base::FilePath custom_folder =
      CreateCustomFolderInUserDownloadsPath("test");
  base::FilePath mount_point_path;
  test_delegate->GetDriveFsMountPointPath(&mount_point_path);
  const auto root_drive_folder = mount_point_path.Append("root");
  const base::FilePath non_root_drive_folder = CreateFolderOnDriveFS("test");
  const base::FilePath onedrive_root =
      test_delegate->GetOneDriveMountPointPath();
  const base::FilePath onedrive_folder = onedrive_root.Append("test");
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    ASSERT_TRUE(base::CreateDirectory(onedrive_folder));
  }
  struct {
    base::FilePath set_save_file_folder;
    CaptureModeSaveToLocation save_location;
  } kTestCases[] = {
      {downloads_folder, CaptureModeSaveToLocation::kDefault},
      {custom_folder, CaptureModeSaveToLocation::kCustomizedFolder},
      {root_drive_folder, CaptureModeSaveToLocation::kDrive},
      {non_root_drive_folder, CaptureModeSaveToLocation::kDriveFolder},
      {onedrive_root, CaptureModeSaveToLocation::kOneDrive},
      {onedrive_folder, CaptureModeSaveToLocation::kOneDriveFolder},
  };
  for (auto test_case : kTestCases) {
    histogram_tester.ExpectBucketCount(histogram_name, test_case.save_location,
                                       0);
  }
  // Set four different save-to locations in clamshell mode and check the
  // histogram results.
  EXPECT_FALSE(Shell::Get()->IsInTabletMode());
  for (auto test_case : kTestCases) {
    StartCaptureSessionWithParam();
    controller->SetCustomCaptureFolder(test_case.set_save_file_folder);
    auto file_saved_path = PerformCapture();
    histogram_tester.ExpectBucketCount(histogram_name, test_case.save_location,
                                       1);
  }

  // Set four different save-to locations in tablet mode and check the histogram
  // results.
  SwitchToTabletMode();
  EXPECT_TRUE(Shell::Get()->IsInTabletMode());
  for (auto test_case : kTestCases) {
    StartCaptureSessionWithParam();
    controller->SetCustomCaptureFolder(test_case.set_save_file_folder);
    auto file_saved_path = PerformCapture();
    histogram_tester.ExpectBucketCount(histogram_name, test_case.save_location,
                                       1);
  }
}

INSTANTIATE_TEST_SUITE_P(All,
                         CaptureModeSaveFileTest,
                         testing::Values(CaptureModeType::kImage,
                                         CaptureModeType::kVideo));

// Test fixture for verifying that the videos are recorded at the pixel size of
// the targets being captured in all recording modes. This avoids having the
// scaling in CopyOutputRequests when performing the capture at a different size
// than that of the render pass (which is in pixels). This scaling causes a loss
// of quality, and a blurry video frames. https://crbug.com/1215185.
class CaptureModeRecordingSizeTest : public CaptureModeTest {
 public:
  CaptureModeRecordingSizeTest() = default;
  ~CaptureModeRecordingSizeTest() override = default;

  // CaptureModeTest:
  void SetUp() override {
    CaptureModeTest::SetUp();
    window_ = CreateTestWindow(gfx::Rect(100, 50, 200, 200));
    CaptureModeController::Get()->SetUserCaptureRegion(user_region_,
                                                       /*by_user=*/true);
    UpdateDisplay("800x600");
  }

  void TearDown() override {
    window_.reset();
    CaptureModeTest::TearDown();
  }

  // Converts the given |size| from DIPs to pixels based on the given value of
  // |dsf|.
  gfx::Size ToPixels(const gfx::Size& size, float dsf) const {
    return gfx::ToFlooredSize(gfx::ConvertSizeToPixels(size, dsf));
  }

 protected:
  // Verifies the size of the received video frame.
  static void VerifyVideoFrame(const gfx::Size& expected_video_size,
                               const media::VideoFrame& frame,
                               const gfx::Rect& content_rect) {
    // The I420 pixel format does not like odd dimensions, so the size of the
    // visible rect in the video frame will be adjusted to be an even value.
    const gfx::Size adjusted_size(expected_video_size.width() & ~1,
                                  expected_video_size.height() & ~1);
    EXPECT_EQ(adjusted_size, frame.visible_rect().size());
  }

  CaptureModeController* StartVideoRecording(CaptureModeSource source) {
    auto* controller = StartCaptureSession(source, CaptureModeType::kVideo);
    if (source == CaptureModeSource::kWindow)
      GetEventGenerator()->MoveMouseToCenterOf(window_.get());
    StartVideoRecordingImmediately();
    EXPECT_TRUE(controller->is_recording_in_progress());
    CaptureModeTestApi().FlushRecordingServiceForTesting();
    return controller;
  }

 protected:
  const gfx::Rect user_region_{20, 50};
  std::unique_ptr<aura::Window> window_;
};

// TODO(crbug.com/1291073): Flaky on ChromeOS.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_CaptureAtPixelsFullscreen DISABLED_CaptureAtPixelsFullscreen
#else
#define MAYBE_CaptureAtPixelsFullscreen CaptureAtPixelsFullscreen
#endif
TEST_F(CaptureModeRecordingSizeTest, MAYBE_CaptureAtPixelsFullscreen) {
  float dsf = 1.6f;
  SetDeviceScaleFactor(dsf);
  EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
  auto* controller = StartVideoRecording(CaptureModeSource::kFullscreen);
  auto* root = window_->GetRootWindow();
  gfx::Size initial_root_window_size_pixels =
      ToPixels(root->bounds().size(), dsf);
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  ASSERT_TRUE(test_delegate->recording_service());
  {
    SCOPED_TRACE("Testing @ 1.6 device scale factor");
    EXPECT_EQ(initial_root_window_size_pixels,
              test_delegate->GetCurrentVideoSize());

    EXPECT_EQ(
        dsf, test_delegate->recording_service()->GetCurrentDeviceScaleFactor());

    EXPECT_EQ(initial_root_window_size_pixels,
              test_delegate->GetCurrentFrameSinkSizeInPixels());

    test_delegate->recording_service()->RequestAndWaitForVideoFrame(
        base::BindOnce(&CaptureModeRecordingSizeTest::VerifyVideoFrame,
                       initial_root_window_size_pixels));
  }

  // Change the DSF and expect the video size will remain at the initial pixel
  // size of the fullscreen.
  dsf = 2.f;
  SetDeviceScaleFactor(dsf);
  EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
  {
    SCOPED_TRACE("Testing @ 2.0 device scale factor");
    EXPECT_EQ(initial_root_window_size_pixels,
              test_delegate->GetCurrentVideoSize());

    // The recording service still tracks the up-to-date DSF and frame sink
    // pixel size even though it doesn't change the video size from its initial
    // value.
    EXPECT_EQ(
        dsf, test_delegate->recording_service()->GetCurrentDeviceScaleFactor());

    EXPECT_EQ(ToPixels(root->bounds().size(), dsf),
              test_delegate->GetCurrentFrameSinkSizeInPixels());

    test_delegate->recording_service()->RequestAndWaitForVideoFrame(
        base::BindOnce(&CaptureModeRecordingSizeTest::VerifyVideoFrame,
                       initial_root_window_size_pixels));
  }

  // When recording the fullscreen, the video size never changes, and remains at
  // the initial pixel size of the recording. Hence, there should be no
  // reconfigures.
  EXPECT_EQ(0, test_delegate->recording_service()
                   ->GetNumberOfVideoEncoderReconfigures());
}

// The test is flaky. https://crbug.com/1287724.
TEST_F(CaptureModeRecordingSizeTest, DISABLED_CaptureAtPixelsRegion) {
  float dsf = 1.6f;
  SetDeviceScaleFactor(dsf);
  EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
  auto* controller = StartVideoRecording(CaptureModeSource::kRegion);
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  ASSERT_TRUE(test_delegate->recording_service());

  {
    SCOPED_TRACE("Testing @ 1.6 device scale factor");
    const gfx::Size expected_video_size = ToPixels(user_region_.size(), dsf);
    EXPECT_EQ(expected_video_size, test_delegate->GetCurrentVideoSize());

    EXPECT_EQ(
        dsf, test_delegate->recording_service()->GetCurrentDeviceScaleFactor());

    test_delegate->recording_service()->RequestAndWaitForVideoFrame(
        base::BindOnce(&CaptureModeRecordingSizeTest::VerifyVideoFrame,
                       expected_video_size));
  }

  // Change the DSF and expect the video size to change to match the new pixel
  // size of the recorded target.
  dsf = 2.f;
  SetDeviceScaleFactor(dsf);
  EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
  {
    SCOPED_TRACE("Testing @ 2.0 device scale factor");
    const gfx::Size expected_video_size = ToPixels(user_region_.size(), dsf);
    EXPECT_EQ(expected_video_size, test_delegate->GetCurrentVideoSize());

    EXPECT_EQ(
        dsf, test_delegate->recording_service()->GetCurrentDeviceScaleFactor());

    test_delegate->recording_service()->RequestAndWaitForVideoFrame(
        base::BindOnce(&CaptureModeRecordingSizeTest::VerifyVideoFrame,
                       expected_video_size));
  }

  // Since the user chooses the capture region in DIPs, its corresponding pixel
  // size will change when changing the device scale factor. Therefore, the
  // encoder is expected to reconfigure once.
  EXPECT_EQ(1, test_delegate->recording_service()
                   ->GetNumberOfVideoEncoderReconfigures());
}

// The test is flaky. https://crbug.com/1287724.
TEST_F(CaptureModeRecordingSizeTest, DISABLED_CaptureAtPixelsWindow) {
  float dsf = 1.6f;
  SetDeviceScaleFactor(dsf);
  EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
  auto* controller = StartVideoRecording(CaptureModeSource::kWindow);
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  ASSERT_TRUE(test_delegate->recording_service());

  {
    SCOPED_TRACE("Testing @ 1.6 device scale factor");
    const gfx::Size expected_video_size =
        ToPixels(window_->GetBoundsInRootWindow().size(), dsf);
    EXPECT_EQ(expected_video_size, test_delegate->GetCurrentVideoSize());

    EXPECT_EQ(
        dsf, test_delegate->recording_service()->GetCurrentDeviceScaleFactor());

    test_delegate->recording_service()->RequestAndWaitForVideoFrame(
        base::BindOnce(&CaptureModeRecordingSizeTest::VerifyVideoFrame,
                       expected_video_size));
  }

  // Change the DSF and expect the video size to change to match the new pixel
  // size of the recorded target.
  dsf = 2.f;
  SetDeviceScaleFactor(dsf);
  EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
  {
    SCOPED_TRACE("Testing @ 2.0 device scale factor");
    const gfx::Size expected_video_size =
        ToPixels(window_->GetBoundsInRootWindow().size(), dsf);
    EXPECT_EQ(expected_video_size, test_delegate->GetCurrentVideoSize());

    EXPECT_EQ(
        dsf, test_delegate->recording_service()->GetCurrentDeviceScaleFactor());

    test_delegate->recording_service()->RequestAndWaitForVideoFrame(
        base::BindOnce(&CaptureModeRecordingSizeTest::VerifyVideoFrame,
                       expected_video_size));
  }

  // When changing the device scale factor, the DIPs size of the window doesn't
  // change, but (like |kRegion|) its pixel size will. Hence, the
  // reconfiguration.
  EXPECT_EQ(1, test_delegate->recording_service()
                   ->GetNumberOfVideoEncoderReconfigures());
}

// Tests the behavior of screen recording with the presence of HDCP secure
// content on the screen in all capture mode sources (fullscreen, region, and
// window) depending on the test param.
class CaptureModeHdcpTest
    : public CaptureModeTest,
      public ::testing::WithParamInterface<CaptureModeSource> {
 public:
  CaptureModeHdcpTest() = default;
  ~CaptureModeHdcpTest() override = default;

  // CaptureModeTest:
  void SetUp() override {
    CaptureModeTest::SetUp();
    window_ = CreateTestWindow(gfx::Rect(200, 200));
    // Create a child window with protected content. This simulates the real
    // behavior of a browser window hosting a page with protected content, where
    // the window that has a protection mask is the RenderWidgetHostViewAura,
    // which is a descendant of the BrowserFrame window which can get recorded.
    protected_content_window_ = CreateTestWindow(gfx::Rect(150, 150));
    window_->AddChild(protected_content_window_.get());
    protection_delegate_ = std::make_unique<OutputProtectionDelegate>(
        protected_content_window_.get());
    CaptureModeController::Get()->SetUserCaptureRegion(gfx::Rect(20, 50),
                                                       /*by_user=*/true);
  }

  void TearDown() override {
    protection_delegate_.reset();
    protected_content_window_.reset();
    window_.reset();
    CaptureModeTest::TearDown();
  }

  // Enters the capture mode session.
  void StartSessionForVideo() {
    StartCaptureSession(GetParam(), CaptureModeType::kVideo);
  }

  // Attempts video recording from the capture mode source set by the test
  // param.
  void AttemptRecording() {
    auto* controller = CaptureModeController::Get();
    ASSERT_TRUE(controller->IsActive());

    switch (GetParam()) {
      case CaptureModeSource::kFullscreen:
      case CaptureModeSource::kRegion:
        controller->StartVideoRecordingImmediatelyForTesting();
        break;

      case CaptureModeSource::kWindow:
        // Window capture mode selects the window under the cursor as the
        // capture source.
        auto* event_generator = GetEventGenerator();
        event_generator->MoveMouseToCenterOf(window_.get());
        controller->StartVideoRecordingImmediatelyForTesting();
        break;
    }
  }

 protected:
  std::unique_ptr<aura::Window> window_;
  std::unique_ptr<aura::Window> protected_content_window_;
  std::unique_ptr<OutputProtectionDelegate> protection_delegate_;
};

TEST_P(CaptureModeHdcpTest, WindowBecomesProtectedWhileRecording) {
  StartSessionForVideo();
  AttemptRecording();
  WaitForRecordingToStart();

  auto* controller = CaptureModeController::Get();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // The window becomes HDCP protected, which should end video recording.
  base::HistogramTester histogram_tester;
  protection_delegate_->SetProtection(display::CONTENT_PROTECTION_METHOD_HDCP,
                                      base::DoNothing());

  EXPECT_FALSE(controller->is_recording_in_progress());
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kHdcpInterruption, 1);
}

TEST_F(CaptureModeHdcpTest, ProtectedTabBecomesActiveAfterRecordingStarts) {
  // Simulate protected content being on an inactive tab.
  protection_delegate_->SetProtection(display::CONTENT_PROTECTION_METHOD_HDCP,
                                      base::DoNothing());
  Shell::GetPrimaryRootWindow()
      ->GetChildById(kShellWindowId_UnparentedContainer)
      ->AddChild(protected_content_window_.get());

  // Recording should start normally, since the protected window is not a
  // descendant of the window being recorded.
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  GetEventGenerator()->MoveMouseToCenterOf(window_.get());
  controller->StartVideoRecordingImmediatelyForTesting();
  WaitForRecordingToStart();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // Simulate activating the tab that has protected content by parenting it back
  // to the window being recorded. Recording should stop immediately.
  window_->AddChild(protected_content_window_.get());

  EXPECT_FALSE(controller->is_recording_in_progress());
}

TEST_P(CaptureModeHdcpTest, ProtectedWindowDestruction) {
  auto window_2 = CreateTestWindow(gfx::Rect(100, 50));
  OutputProtectionDelegate protection_delegate_2(window_2.get());
  protection_delegate_2.SetProtection(display::CONTENT_PROTECTION_METHOD_HDCP,
                                      base::DoNothing());

  StartSessionForVideo();
  AttemptRecording();

  // Recording cannot start because of another protected window on the screen,
  // except when we're capturing a different |window_|.
  auto* controller = CaptureModeController::Get();
  EXPECT_FALSE(controller->IsActive());
  if (GetParam() == CaptureModeSource::kWindow) {
    WaitForRecordingToStart();
    EXPECT_TRUE(controller->is_recording_in_progress());
    controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
    EXPECT_FALSE(controller->is_recording_in_progress());
    // Wait for the video file to be saved so that we can start a new recording.
    WaitForCaptureFileToBeSaved();
  } else {
    EXPECT_FALSE(controller->is_recording_in_progress());
  }

  // When the protected window is destroyed, it's possbile now to record from
  // all capture sources.
  window_2.reset();
  StartSessionForVideo();
  AttemptRecording();
  WaitForRecordingToStart();

  EXPECT_FALSE(controller->IsActive());
  EXPECT_TRUE(controller->is_recording_in_progress());
}

TEST_P(CaptureModeHdcpTest, WindowBecomesProtectedBeforeRecording) {
  protection_delegate_->SetProtection(display::CONTENT_PROTECTION_METHOD_HDCP,
                                      base::DoNothing());
  StartSessionForVideo();
  AttemptRecording();

  // Recording cannot even start.
  auto* controller = CaptureModeController::Get();
  EXPECT_FALSE(controller->is_recording_in_progress());
  EXPECT_FALSE(controller->IsActive());
}

TEST_P(CaptureModeHdcpTest, ProtectedWindowInMultiDisplay) {
  UpdateDisplay("500x400,401+0-500x400");
  auto roots = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, roots.size());
  protection_delegate_->SetProtection(display::CONTENT_PROTECTION_METHOD_HDCP,
                                      base::DoNothing());

  // Move the cursor to the secondary display before starting the session to
  // make sure the session starts on that display.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(roots[1]->GetBoundsInScreen().CenterPoint());
  StartSessionForVideo();
  // Also, make sure the selected region is in the secondary display.
  auto* controller = CaptureModeController::Get();
  EXPECT_EQ(controller->capture_mode_session()->current_root(), roots[1]);
  AttemptRecording();

  // Recording should be able to start (since the protected window is on the
  // first display) unless the protected window itself is the one being
  // recorded.
  if (GetParam() == CaptureModeSource::kWindow) {
    EXPECT_FALSE(controller->is_recording_in_progress());
  } else {
    WaitForRecordingToStart();
    EXPECT_TRUE(controller->is_recording_in_progress());

    // Moving the protected window to the display being recorded should
    // terminate the recording.
    base::HistogramTester histogram_tester;
    window_util::MoveWindowToDisplay(window_.get(),
                                     roots[1]->GetHost()->GetDisplayId());
    ASSERT_EQ(window_->GetRootWindow(), roots[1]);
    ASSERT_EQ(protected_content_window_->GetRootWindow(), roots[1]);
    EXPECT_FALSE(controller->is_recording_in_progress());
    histogram_tester.ExpectBucketCount(
        kEndRecordingReasonInClamshellHistogramName,
        EndRecordingReason::kHdcpInterruption, 1);
  }
}

INSTANTIATE_TEST_SUITE_P(All,
                         CaptureModeHdcpTest,
                         testing::Values(CaptureModeSource::kFullscreen,
                                         CaptureModeSource::kRegion,
                                         CaptureModeSource::kWindow));

TEST_F(CaptureModeTest, ClosingWindowBeingRecorded) {
  auto window = CreateTestWindow(gfx::Rect(200, 200));
  StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window.get());
  auto* controller = CaptureModeController::Get();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // The window should have a valid capture ID.
  EXPECT_TRUE(window->subtree_capture_id().is_valid());

  // Generate a couple of mouse moves, so that the second one gets throttled
  // using the `VideoRecordingWatcher::cursor_events_throttle_timer_`. This is
  // needed for a regression testing of https://crbug.com/1273609.
  event_generator->MoveMouseBy(20, 30);
  event_generator->MoveMouseBy(-10, -20);

  // Closing the window being recorded should end video recording.
  base::HistogramTester histogram_tester;
  window.reset();

  auto* stop_recording_button = Shell::GetPrimaryRootWindowController()
                                    ->GetStatusAreaWidget()
                                    ->stop_recording_button_tray();
  EXPECT_FALSE(stop_recording_button->visible_preferred());
  EXPECT_FALSE(controller->is_recording_in_progress());
  WaitForCaptureFileToBeSaved();
  EXPECT_FALSE(controller->video_recording_watcher_for_testing());
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kDisplayOrWindowClosing, 1);
}

TEST_F(CaptureModeTest, DetachDisplayWhileWindowRecording) {
  UpdateDisplay("500x400,401+0-500x400");
  // Create a window on the second display.
  auto window = CreateTestWindow(gfx::Rect(450, 20, 200, 200));
  auto roots = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, roots.size());
  EXPECT_EQ(window->GetRootWindow(), roots[1]);
  StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(window->GetBoundsInScreen().CenterPoint());
  auto* controller = CaptureModeController::Get();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  auto* stop_recording_button = RootWindowController::ForWindow(roots[1])
                                    ->GetStatusAreaWidget()
                                    ->stop_recording_button_tray();
  EXPECT_TRUE(stop_recording_button->visible_preferred());

  // Disconnecting the display, on which the window being recorded is located,
  // should not end the recording. The window should be reparented to another
  // display, and the stop-recording button should move with to that display.
  RemoveSecondaryDisplay();
  roots = Shell::GetAllRootWindows();
  ASSERT_EQ(1u, roots.size());

  EXPECT_TRUE(controller->is_recording_in_progress());
  stop_recording_button = RootWindowController::ForWindow(roots[0])
                              ->GetStatusAreaWidget()
                              ->stop_recording_button_tray();
  EXPECT_TRUE(stop_recording_button->visible_preferred());
}

TEST_F(CaptureModeTest, SuspendWhileSessionIsActive) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  EXPECT_TRUE(controller->IsActive());
  power_manager_client()->SendSuspendImminent(
      power_manager::SuspendImminent::IDLE);
  EXPECT_FALSE(controller->IsActive());
}

TEST_F(CaptureModeTest, SuspendAfterCountdownStarts) {
  // User NORMAL_DURATION for the countdown animation so we can have predictable
  // timings.
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  // Hit Enter to begin recording, wait for 1 second, then suspend the device.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_RETURN, event_generator);
  WaitForSeconds(1);
  power_manager_client()->SendSuspendImminent(
      power_manager::SuspendImminent::IDLE);
  EXPECT_FALSE(controller->IsActive());
  EXPECT_FALSE(controller->is_recording_in_progress());
}

TEST_F(CaptureModeTest, SuspendAfterRecordingStarts) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());
  base::HistogramTester histogram_tester;
  power_manager_client()->SendSuspendImminent(
      power_manager::SuspendImminent::IDLE);
  EXPECT_FALSE(controller->is_recording_in_progress());
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kImminentSuspend, 1);
}

TEST_F(CaptureModeTest, SwitchUsersWhileRecording) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  StartVideoRecordingImmediately();
  base::HistogramTester histogram_tester;
  EXPECT_TRUE(controller->is_recording_in_progress());
  SwitchToUser2();
  EXPECT_FALSE(controller->is_recording_in_progress());
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kActiveUserChange, 1);
}

TEST_F(CaptureModeTest, SwitchUsersAfterCountdownStarts) {
  // User NORMAL_DURATION for the countdown animation so we can have predictable
  // timings.
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  // Hit Enter to begin recording, wait for 1 second, then switch users.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_RETURN, event_generator);
  WaitForSeconds(1);
  SwitchToUser2();
  EXPECT_FALSE(controller->IsActive());
  EXPECT_FALSE(controller->is_recording_in_progress());
}

TEST_F(CaptureModeTest, ClosingDisplayBeingFullscreenRecorded) {
  UpdateDisplay("500x400,401+0-500x400");
  auto roots = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, roots.size());
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(roots[1]->GetBoundsInScreen().CenterPoint());
  auto* controller = CaptureModeController::Get();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  auto* stop_recording_button = RootWindowController::ForWindow(roots[1])
                                    ->GetStatusAreaWidget()
                                    ->stop_recording_button_tray();
  EXPECT_TRUE(stop_recording_button->visible_preferred());

  // Disconnecting the display being fullscreen recorded should end the
  // recording and remove the stop recording button.
  base::HistogramTester histogram_tester;
  RemoveSecondaryDisplay();
  roots = Shell::GetAllRootWindows();
  ASSERT_EQ(1u, roots.size());

  EXPECT_FALSE(controller->is_recording_in_progress());
  stop_recording_button = RootWindowController::ForWindow(roots[0])
                              ->GetStatusAreaWidget()
                              ->stop_recording_button_tray();
  EXPECT_FALSE(stop_recording_button->visible_preferred());
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kDisplayOrWindowClosing, 1);
}

TEST_F(CaptureModeTest, ShuttingDownWhileRecording) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);

  auto* controller = CaptureModeController::Get();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // Exiting the test now will shut down ash while recording is in progress,
  // there should be no crashes when
  // VideoRecordingWatcher::OnChromeTerminating() terminates the recording.
}

// Tests that metrics are recorded properly for capture mode bar buttons.
TEST_F(CaptureModeTest, CaptureModeBarButtonTypeHistograms) {
  constexpr char kClamshellHistogram[] =
      "Ash.CaptureModeController.BarButtons.ClamshellMode";
  constexpr char kTabletHistogram[] =
      "Ash.CaptureModeController.BarButtons.TabletMode";
  base::HistogramTester histogram_tester;

  CaptureModeController::Get()->Start(CaptureModeEntryType::kQuickSettings);
  auto* event_generator = GetEventGenerator();

  // Tests each bar button in clamshell mode.
  ClickOnView(GetImageToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(
      kClamshellHistogram, CaptureModeBarButtonType::kScreenCapture, 1);

  ClickOnView(GetVideoToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(
      kClamshellHistogram, CaptureModeBarButtonType::kScreenRecord, 1);

  ClickOnView(GetFullscreenToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(kClamshellHistogram,
                                     CaptureModeBarButtonType::kFull, 1);

  ClickOnView(GetRegionToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(kClamshellHistogram,
                                     CaptureModeBarButtonType::kRegion, 1);

  ClickOnView(GetWindowToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(kClamshellHistogram,
                                     CaptureModeBarButtonType::kWindow, 1);

  // Enter tablet mode and test the bar buttons.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());

  ClickOnView(GetImageToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(
      kTabletHistogram, CaptureModeBarButtonType::kScreenCapture, 1);

  ClickOnView(GetVideoToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(
      kTabletHistogram, CaptureModeBarButtonType::kScreenRecord, 1);

  ClickOnView(GetFullscreenToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(kTabletHistogram,
                                     CaptureModeBarButtonType::kFull, 1);

  ClickOnView(GetRegionToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(kTabletHistogram,
                                     CaptureModeBarButtonType::kRegion, 1);

  ClickOnView(GetWindowToggleButton(), event_generator);
  histogram_tester.ExpectBucketCount(kTabletHistogram,
                                     CaptureModeBarButtonType::kWindow, 1);
}

TEST_F(CaptureModeTest, CaptureSessionSwitchedModeMetric) {
  constexpr char kHistogramName[] =
      "Ash.CaptureModeController.SwitchesFromInitialCaptureMode";
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramName, false, 0);
  histogram_tester.ExpectBucketCount(kHistogramName, true, 0);

  // Perform a capture without switching modes. A false should be recorded.
  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(100, 100));
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_RETURN, event_generator);
  histogram_tester.ExpectBucketCount(kHistogramName, false, 1);
  histogram_tester.ExpectBucketCount(kHistogramName, true, 0);

  // Perform a capture after switching to fullscreen mode. A true should be
  // recorded.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  ClickOnView(GetFullscreenToggleButton(), event_generator);
  SendKey(ui::VKEY_RETURN, event_generator);
  histogram_tester.ExpectBucketCount(kHistogramName, false, 1);
  histogram_tester.ExpectBucketCount(kHistogramName, true, 1);

  // Perform a capture after switching to another mode and back to the original
  // mode. A true should still be recorded as there was some switching done.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  ClickOnView(GetRegionToggleButton(), event_generator);
  ClickOnView(GetFullscreenToggleButton(), event_generator);
  SendKey(ui::VKEY_RETURN, event_generator);
  histogram_tester.ExpectBucketCount(kHistogramName, false, 1);
  histogram_tester.ExpectBucketCount(kHistogramName, true, 2);
}

// Test that cancel recording during countdown won't cause crash.
TEST_F(CaptureModeTest, CancelCaptureDuringCountDown) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  // Hit Enter to begin recording, Wait for 1 second, then press ESC while count
  // down is in progress.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_RETURN, event_generator);
  WaitForSeconds(1);
  CaptureModeTestApi test_api;
  EXPECT_TRUE(test_api.IsInCountDownAnimation());
  SendKey(ui::VKEY_ESCAPE, event_generator);
  EXPECT_FALSE(test_api.IsInCountDownAnimation());
  EXPECT_FALSE(test_api.IsSessionActive());
  EXPECT_FALSE(test_api.IsVideoRecordingInProgress());
}

TEST_F(CaptureModeTest, EscDuringCountDownWhileSettingsOpen) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);

  // Hitting Esc while the settings menu is open and the count down is in
  // progress should end the session directly.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_TRUE(GetCaptureModeSettingsWidget());
  SendKey(ui::VKEY_RETURN, event_generator);
  WaitForSeconds(1);
  CaptureModeTestApi test_api;
  EXPECT_TRUE(test_api.IsInCountDownAnimation());
  SendKey(ui::VKEY_ESCAPE, event_generator);
  EXPECT_FALSE(test_api.IsInCountDownAnimation());
  EXPECT_FALSE(test_api.IsSessionActive());
  EXPECT_FALSE(test_api.IsVideoRecordingInProgress());
}

// Tests that metrics are recorded properly for capture region adjustments.
TEST_F(CaptureModeTest, NumberOfCaptureRegionAdjustmentsHistogram) {
  constexpr char kClamshellHistogram[] =
      "Ash.CaptureModeController.CaptureRegionAdjusted.ClamshellMode";
  constexpr char kTabletHistogram[] =
      "Ash.CaptureModeController.CaptureRegionAdjusted.TabletMode";
  base::HistogramTester histogram_tester;
  UpdateDisplay("800x700");

  auto* controller = StartImageRegionCapture();
  // Create the initial region.
  const gfx::Rect target_region(gfx::Rect(200, 200, 400, 400));
  SelectRegion(target_region);

  auto resize_and_reset_region = [](ui::test::EventGenerator* event_generator,
                                    const gfx::Point& top_right) {
    // Enlarges the region and then resize it back to its original size.
    event_generator->set_current_screen_location(top_right);
    event_generator->DragMouseTo(top_right + gfx::Vector2d(50, 50));
    event_generator->DragMouseTo(top_right);
  };

  auto move_and_reset_region = [](ui::test::EventGenerator* event_generator,
                                  const gfx::Point& drag_point) {
    // Moves the region and then moves it back to its original position.
    event_generator->set_current_screen_location(drag_point);
    event_generator->DragMouseTo(drag_point + gfx::Vector2d(-50, -50));
    event_generator->DragMouseTo(drag_point);
  };

  // Resize the region twice by dragging the top right of the region out and
  // then back again.
  auto* event_generator = GetEventGenerator();
  auto top_right = target_region.top_right();
  resize_and_reset_region(event_generator, top_right);

  // Move the region twice by dragging within the region.
  const gfx::Point drag_point(300, 300);
  move_and_reset_region(event_generator, drag_point);

  // Perform a capture to record the count.
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(kClamshellHistogram, 4, 1);

  // Create a new image region capture. Move the region twice then change
  // sources to fullscreen and back to region. This toggle should reset the
  // count. Perform a capture to record the count.
  StartImageRegionCapture();
  move_and_reset_region(event_generator, drag_point);
  controller->SetSource(CaptureModeSource::kFullscreen);
  controller->SetSource(CaptureModeSource::kRegion);
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(kClamshellHistogram, 0, 1);

  // Enter tablet mode and restart the capture session. The capture region
  // should be remembered.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  StartImageRegionCapture();
  ASSERT_EQ(target_region, controller->user_capture_region());

  // Resize the region twice by dragging the top right of the region out and
  // then back again.
  resize_and_reset_region(event_generator, top_right);

  // Move the region twice by dragging within the region.
  move_and_reset_region(event_generator, drag_point);

  // Perform a capture to record the count.
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(kTabletHistogram, 4, 1);

  // Restart the region capture and resize it. Then create a new region by
  // dragging outside of the existing capture region. This should reset the
  // counter. Change source to record a sample.
  StartImageRegionCapture();
  resize_and_reset_region(event_generator, top_right);
  SelectRegion(gfx::Rect(0, 0, 100, 100));
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(kTabletHistogram, 0, 1);
}

TEST_F(CaptureModeTest, ResizeRegionBoundedByDisplay) {
  UpdateDisplay("800x700");

  auto* controller = StartImageRegionCapture();
  ASSERT_TRUE(controller->IsActive());
  ASSERT_EQ(CaptureModeSource::kRegion, controller->source());

  // Attempt to create a new region that goes outside of the display bounds.
  gfx::Rect target_region(gfx::Rect(200, 200, 800, 800));
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(target_region.origin());
  event_generator->PressLeftButton();
  event_generator->MoveMouseTo(target_region.bottom_right());
  event_generator->ReleaseLeftButton();

  // The region should stay within the display bounds.
  ASSERT_TRUE(
      controller->capture_mode_session()->current_root()->bounds().Contains(
          controller->user_capture_region()));
  EXPECT_EQ(gfx::Rect(200, 200, 600, 500), controller->user_capture_region());

  // Attempt to adjust the existing region outside of the display bounds.
  event_generator->set_current_screen_location(target_region.origin());
  event_generator->PressLeftButton();
  event_generator->MoveMouseTo(gfx::Point(-100, -100));
  event_generator->ReleaseLeftButton();

  // The region should stay within the display bounds.
  ASSERT_TRUE(
      controller->capture_mode_session()->current_root()->bounds().Contains(
          controller->user_capture_region()));
  EXPECT_EQ(gfx::Rect(0, 0, 800, 700), controller->user_capture_region());
}

TEST_F(CaptureModeTest, FullscreenCapture) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  CaptureModeController* controller = StartCaptureSession(
      CaptureModeSource::kFullscreen, CaptureModeType::kImage);
  EXPECT_TRUE(controller->IsActive());
  // Press anywhere to capture image.
  auto* event_generator = GetEventGenerator();
  event_generator->ClickLeftButton();
  EXPECT_FALSE(controller->IsActive());

  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                   CaptureModeType::kVideo);
  EXPECT_TRUE(controller->IsActive());
  // Press anywhere to capture video.
  event_generator->ClickLeftButton();
  WaitForRecordingToStart();
  EXPECT_FALSE(controller->IsActive());
}

// Tests that there is no crash when touching the capture label widget in tablet
// mode when capturing a window. Regression test for https://crbug.com/1152938.
TEST_F(CaptureModeTest, TabletTouchCaptureLabelWidgetWindowMode) {
  SwitchToTabletMode();

  // Enter capture window mode.
  CaptureModeController* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  ASSERT_TRUE(controller->IsActive());

  // Press and release on where the capture label widget would be.
  auto* event_generator = GetEventGenerator();
  DCHECK(GetCaptureModeLabelWidget());
  event_generator->set_current_screen_location(
      GetCaptureModeLabelWidget()->GetWindowBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  event_generator->ReleaseTouch();

  // There are no windows and home screen window is excluded from window capture
  // mode, so capture mode will still remain active.
  EXPECT_TRUE(Shell::Get()->app_list_controller()->IsHomeScreenVisible());
  EXPECT_TRUE(controller->IsActive());
}

// Tests that after rotating a display, the capture session widgets are updated
// and the capture region is reset.
TEST_F(CaptureModeTest, DisplayRotation) {
  UpdateDisplay("1200x600");

  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(1200, 400));
  OpenSettingsView();

  // Rotate the primary display by 90 degrees. Test that the region, capture
  // bar and capture settings fit within the rotated bounds, and the capture
  // label widget is still centered in the region.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(), display::Display::ROTATE_90,
      display::Display::RotationSource::USER);
  const gfx::Rect rotated_root_bounds(600, 1200);
  EXPECT_TRUE(rotated_root_bounds.Contains(controller->user_capture_region()));
  const gfx::Rect capture_bar_bounds =
      GetCaptureModeBarView()->GetBoundsInScreen();
  const gfx::Rect settings_bounds =
      CaptureModeSettingsTestApi().GetSettingsView()->GetBoundsInScreen();
  EXPECT_TRUE(rotated_root_bounds.Contains(capture_bar_bounds));
  EXPECT_TRUE(rotated_root_bounds.Contains(settings_bounds));
  // Verify that the space between the bottom of the settings and the top
  // of the capture bar is `kSpaceBetweenCaptureBarAndSettingsMenu`.
  EXPECT_EQ(capture_bar_bounds.y() - settings_bounds.bottom(),
            capture_mode::kSpaceBetweenCaptureBarAndSettingsMenu);
  views::Widget* capture_label_widget = GetCaptureModeLabelWidget();
  ASSERT_TRUE(capture_label_widget);
  EXPECT_EQ(controller->user_capture_region().CenterPoint(),
            capture_label_widget->GetWindowBoundsInScreen().CenterPoint());
}

TEST_F(CaptureModeTest, DisplayBoundsChange) {
  UpdateDisplay("1200x600");

  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(1200, 400));

  // Shrink the display. The capture region should shrink, and the capture bar
  // should be adjusted to be centered.
  UpdateDisplay("700x600");
  EXPECT_EQ(gfx::Rect(700, 400), controller->user_capture_region());
  EXPECT_EQ(350,
            GetCaptureModeBarView()->GetBoundsInScreen().CenterPoint().x());
}

TEST_F(CaptureModeTest, ReenterOnSmallerDisplay) {
  UpdateDisplay("1200x600,1201+0-700x600");

  // Start off with the primary display as the targeted display. Create a region
  // that fits the primary display but would be too big for the secondary
  // display.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(700, 300));
  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(1200, 400));
  EXPECT_EQ(gfx::Rect(1200, 400), controller->user_capture_region());
  controller->Stop();

  // Make the secondary display the targeted display. Test that the region has
  // shrunk to fit the display.
  event_generator->MoveMouseTo(gfx::Point(1500, 300));
  StartImageRegionCapture();
  EXPECT_EQ(gfx::Rect(700, 400), controller->user_capture_region());
}

// Tests tabbing when in capture window mode.
TEST_F(CaptureModeTest, KeyboardNavigationBasic) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  // Initially nothing is focused.
  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Tab once, we are now focusing the type and source buttons group on the
  // capture bar.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Tab four times to focus the last source button (window mode button).
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/4);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());

  // Tab once to focus the settings and close buttons group on the capture bar.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Shift tab to focus the last source button again.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());

  // Press esc to clear focus, but remain in capture mode.
  SendKey(ui::VKEY_ESCAPE, event_generator);
  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_TRUE(controller->IsActive());

  // Tests that pressing esc when there is no focus will exit capture mode.
  SendKey(ui::VKEY_ESCAPE, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

// Tests tabbing through windows on multiple displays when in capture window
// mode.
TEST_F(CaptureModeTest, KeyboardNavigationTabThroughWindowsOnMultipleDisplays) {
  UpdateDisplay("800x700,801+0-800x700");
  std::vector<raw_ptr<aura::Window, VectorExperimental>> root_windows =
      Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());

  // Create three windows, one of them is a modal transient child.
  std::unique_ptr<aura::Window> window1(
      CreateTestWindow(gfx::Rect(0, 0, 200, 200)));
  auto window1_transient = CreateTransientModalChildWindow(
      gfx::Rect(20, 30, 200, 150), window1.get());
  std::unique_ptr<aura::Window> window2(
      CreateTestWindow(gfx::Rect(900, 0, 200, 200)));

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* capture_mode_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_mode_session->session_type(), SessionType::kReal);

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi test_api(capture_mode_session);

  // Initially nothing is focused.
  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Tab five times, we are now focusing the window mode button on the
  // capture bar.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/5);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());

  // Enter space to select window mode.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(CaptureModeSource::kWindow, controller->source());

  // Tab once, we are now focusing |window2| and capture mode bar is on
  // display2.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(root_windows[1], capture_mode_session->current_root());

  // Tab once, we are now focusing |window1_transient|. Since
  // |window1_transient| is on display1, capture mode bar will be moved to
  // display1 as well.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(window1_transient.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(root_windows[0], capture_mode_session->current_root());

  // Tab once, we are now focusing |window1|. Capture mode bar still stays on
  // display1.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(2u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(window1.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(root_windows[0], capture_mode_session->current_root());

  // Press space, make sure nothing is changed and no crash.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_TRUE(controller->IsActive());
  EXPECT_EQ(window1.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(root_windows[0], capture_mode_session->current_root());

  // Tab once to focus the settings and close buttons group on the capture bar.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Shift tab to focus |window1| again.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(2u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(window1.get(), capture_mode_session->GetSelectedWindow());

  // Shift tab to focus |window1_transient|.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(window1_transient.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(root_windows[0], capture_mode_session->current_root());

  // Shift tab to focus |window2|. Capture mode bar will be moved to display2 as
  // well.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(root_windows[1], capture_mode_session->current_root());

  // Press esc to clear focus, but remain in capture mode with |window2|
  // selected.
  SendKey(ui::VKEY_ESCAPE, event_generator);
  EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
  EXPECT_TRUE(controller->IsActive());
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(root_windows[1], capture_mode_session->current_root());

  // Press return. Since there's a selected window, capture mode will
  // be ended after capturing the selected window.
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

// Tests that a click will remove focus.
TEST_F(CaptureModeTest, KeyboardNavigationClicksRemoveFocus) {
  auto* controller = StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();

  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_TRUE(test_api.HasFocus());

  event_generator->ClickLeftButton();
  EXPECT_FALSE(test_api.HasFocus());
}

// Tests that pressing space on a focused button will activate it.
TEST_F(CaptureModeTest, KeyboardNavigationSpaceToActivateButton) {
  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(200, 200));

  auto* event_generator = GetEventGenerator();

  // Tab to the button which changes the capture type to video and hit space.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/2);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(CaptureModeType::kVideo, controller->type());

  // Shift tab and space to change the capture type back to image.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(CaptureModeType::kImage, controller->type());

  // Tab to the fullscreen button and hit space.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/2);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(CaptureModeSource::kFullscreen, controller->source());

  // Tab to the region button and hit space to return to region capture mode.
  SendKey(ui::VKEY_TAB, event_generator);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(CaptureModeSource::kRegion, controller->source());

  // Tab to the capture button and hit space to perform a capture, which exits
  // capture mode.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/11);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

// Tests that functionality to create and adjust a region with keyboard
// shortcuts works as intended.
TEST_F(CaptureModeTest, KeyboardNavigationSelectRegion) {
  auto* controller = StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();
  ASSERT_TRUE(controller->user_capture_region().IsEmpty());

  // Test that hitting space will create a default region.
  SendKey(ui::VKEY_SPACE, event_generator);
  gfx::Rect capture_region = controller->user_capture_region();
  EXPECT_FALSE(capture_region.IsEmpty());

  // Test that hitting an arrow key will do nothing as the selection region is
  // not focused initially.
  SendKey(ui::VKEY_RIGHT, event_generator);
  EXPECT_EQ(capture_region, controller->user_capture_region());

  const int arrow_shift = capture_mode::kArrowKeyboardRegionChangeDp;

  // Hit tab until the whole region is focused.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  EXPECT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kSelection,
            test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Arrow keys should shift the whole region.
  SendKey(ui::VKEY_RIGHT, event_generator);
  EXPECT_EQ(capture_region.origin() + gfx::Vector2d(arrow_shift, 0),
            controller->user_capture_region().origin());
  EXPECT_EQ(capture_region.size(), controller->user_capture_region().size());
  SendKey(ui::VKEY_RIGHT, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(
      capture_region.origin() +
          gfx::Vector2d(
              arrow_shift + capture_mode::kShiftArrowKeyboardRegionChangeDp, 0),
      controller->user_capture_region().origin());
  EXPECT_EQ(capture_region.size(), controller->user_capture_region().size());

  // Hit tab so that the top left affordance circle is focused. Left and up keys
  // should enlarge the region, right and bottom keys should shrink the region.
  capture_region = controller->user_capture_region();
  SendKey(ui::VKEY_TAB, event_generator);
  SendKey(ui::VKEY_LEFT, event_generator);
  SendKey(ui::VKEY_UP, event_generator);
  EXPECT_EQ(capture_region.size() + gfx::Size(arrow_shift, arrow_shift),
            controller->user_capture_region().size());
  SendKey(ui::VKEY_RIGHT, event_generator);
  SendKey(ui::VKEY_DOWN, event_generator);
  EXPECT_EQ(capture_region.size(), controller->user_capture_region().size());

  // Tab until we focus the bottom right affordance circle. Left and up keys
  // should shrink the region, right and bottom keys should enlarge the region.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/4);

  SendKey(ui::VKEY_LEFT, event_generator);
  SendKey(ui::VKEY_UP, event_generator);
  EXPECT_EQ(capture_region.size() - gfx::Size(arrow_shift, arrow_shift),
            controller->user_capture_region().size());
  SendKey(ui::VKEY_RIGHT, event_generator);
  SendKey(ui::VKEY_DOWN, event_generator);
  EXPECT_EQ(capture_region.size(), controller->user_capture_region().size());
}

// Tests behavior regarding the default region when using keyboard navigation.
TEST_F(CaptureModeTest, KeyboardNavigationDefaultRegion) {
  auto* controller = StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();
  ASSERT_TRUE(controller->user_capture_region().IsEmpty());

  // Hit space when nothing is focused to get the expected default capture
  // region.
  SendKey(ui::VKEY_SPACE, event_generator);
  const gfx::Rect expected_default_region = controller->user_capture_region();
  SelectRegion(gfx::Rect(20, 20, 200, 200));

  // Hit space when there is an existing region. Tests that the region remains
  // unchanged.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(gfx::Rect(20, 20, 200, 200), controller->user_capture_region());

  // Tab to the image toggle button. Tests that hitting space does not change
  // the region size.
  SelectRegion(gfx::Rect());
  SendKey(ui::VKEY_TAB, event_generator);
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kTypeSource,
            test_api.GetCurrentFocusGroup());
  ASSERT_EQ(0u, test_api.GetCurrentFocusIndex());

  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(gfx::Rect(), controller->user_capture_region());

  // Tests that hitting space while focusing the region toggle button when in
  // region capture mode will make the capture region the default size.
  // SelectRegion removes focus since it uses mouse clicks.
  SelectRegion(gfx::Rect());
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE,
          /*count=*/4);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(expected_default_region, controller->user_capture_region());

  // Tests that hitting space while focusing the region toggle button when not
  // in region capture mode does nothing to the capture region.
  SelectRegion(gfx::Rect());
  ClickOnView(GetWindowToggleButton(), event_generator);
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE,
          /*count=*/4);
  ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kTypeSource,
            test_api.GetCurrentFocusGroup());
  ASSERT_EQ(3u, test_api.GetCurrentFocusIndex());
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(gfx::Rect(), controller->user_capture_region());
}

TEST_F(CaptureModeTest, A11yEnterWithNoFocus) {
  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(20, 20, 200, 200));
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kNone,
            test_api.GetCurrentFocusGroup());

  // When nothing is focused, the `Enter` key should perform the capture.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

TEST_F(CaptureModeTest, A11yEnterWithFocusOnRegionKnob) {
  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(20, 20, 200, 200));
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  // Tab until you reach the region adjustment focus group.
  auto* event_generator = GetEventGenerator();
  while (test_api.GetCurrentFocusGroup() !=
         CaptureModeSessionFocusCycler::FocusGroup::kSelection) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // Tab twice more to be on one of the knobs.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/2);
  ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kSelection,
            test_api.GetCurrentFocusGroup());

  // Enter should perform capture.
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

TEST_F(CaptureModeTest, A11yEnterWithFocusOnWindow) {
  auto window = CreateTestWindow(gfx::Rect(200, 200));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);

  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  // Tab until you reach the window to be captured.
  auto* event_generator = GetEventGenerator();
  while (test_api.GetCurrentFocusGroup() !=
         CaptureModeSessionFocusCycler::FocusGroup::kCaptureWindow) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // Enter should perform capture.
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

TEST_F(CaptureModeTest, A11yEnterWithFocusOnFullscreenButton) {
  auto* controller = StartImageRegionCapture();
  EXPECT_EQ(controller->source(), CaptureModeSource::kRegion);
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  // Tab once to enter focus into the bar.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kTypeSource,
            test_api.GetCurrentFocusGroup());

  // Tab until you reach the fullscreen toggle button.
  auto* fullscreen_toggle_button = test_api.GetCaptureModeBarView()
                                       ->GetCaptureSourceView()
                                       ->fullscreen_toggle_button();
  while (test_api.GetCurrentFocusedView()->GetView() !=
         fullscreen_toggle_button) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // The first Enter will switch the source to `kFullscreen`.
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_TRUE(controller->IsActive());
  EXPECT_EQ(controller->source(), CaptureModeSource::kFullscreen);

  // The focus should not change.
  EXPECT_EQ(test_api.GetCurrentFocusedView()->GetView(),
            fullscreen_toggle_button);

  // The second Enter should perform capture.
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

// Tests that the UAF issue caused by `NotifyAccessibilityEvent` after the
// button been destroyed has been handled without leading to a crash.
TEST_F(CaptureModeTest, KeyboardNavigationButtonDestroyedAfterBeenActivated) {
  auto* controller = StartImageRegionCapture();
  SelectRegion(gfx::Rect(200, 300));

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* event_generator = GetEventGenerator();

  // Tab 15 times to reach the capture button and press space key to activate
  // the button.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/15);
  EXPECT_EQ(FocusGroup::kCaptureButton,
            CaptureModeSessionTestApi(controller->capture_mode_session())
                .GetCurrentFocusGroup());
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_FALSE(controller->IsActive());

  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                   CaptureModeType::kImage);

  // Tab 7 times to reach the close button and press space key to activate the
  // button.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/7);
  EXPECT_EQ(FocusGroup::kSettingsClose,
            CaptureModeSessionTestApi(controller->capture_mode_session())
                .GetCurrentFocusGroup());
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_FALSE(controller->IsActive());
}

// Tests that accessibility overrides are set as expected on capture mode
// widgets.
TEST_F(CaptureModeTest, AccessibilityFocusAnnotator) {
  StartImageRegionCapture();

  // Helper that takes in a current widget and checks if the accessibility next
  // and previous focus widgets match the given.
  auto check_a11y_overrides = [](const std::string& id, views::Widget* widget,
                                 views::Widget* expected_previous,
                                 views::Widget* expected_next) -> void {
    SCOPED_TRACE(id);
    views::View* contents_view = widget->GetContentsView();
    views::ViewAccessibility& view_accessibility =
        contents_view->GetViewAccessibility();
    EXPECT_EQ(expected_previous, view_accessibility.GetPreviousWindowFocus());
    EXPECT_EQ(expected_next, view_accessibility.GetNextWindowFocus());
  };

  // With no region, there is no capture label button and no settings menu
  // opened, so the bar is the only focusable capture session widget.
  views::Widget* bar_widget = GetCaptureModeBarWidget();
  check_a11y_overrides("bar", bar_widget, nullptr, nullptr);

  // With a region, the focus should go from the bar widget to the label widget
  // and back.
  SendKey(ui::VKEY_SPACE, GetEventGenerator());
  views::Widget* label_widget = GetCaptureModeLabelWidget();
  check_a11y_overrides("bar", bar_widget, label_widget, label_widget);
  check_a11y_overrides("label", label_widget, bar_widget, bar_widget);

  // With a settings menu open, the focus should go from the bar widget to the
  // label widget to the settings widget and back to the bar widget.
  ClickOnView(GetSettingsButton(), GetEventGenerator());
  views::Widget* settings_widget = GetCaptureModeSettingsWidget();
  ASSERT_TRUE(settings_widget);
  check_a11y_overrides("bar", bar_widget, settings_widget, label_widget);
  check_a11y_overrides("label", label_widget, bar_widget, settings_widget);
  check_a11y_overrides("settings", settings_widget, label_widget, bar_widget);
}

// Tests that a captured image is written to the clipboard.
TEST_F(CaptureModeTest, ClipboardWrite) {
  auto* clipboard = ui::Clipboard::GetForCurrentThread();
  ASSERT_NE(clipboard, nullptr);

  const ui::ClipboardSequenceNumberToken before_sequence_number =
      clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste);

  CaptureNotificationWaiter waiter;
  CaptureModeController::Get()->CaptureScreenshotsOfAllDisplays();
  waiter.Wait();

  const ui::ClipboardSequenceNumberToken after_sequence_number =
      clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste);

  EXPECT_NE(before_sequence_number, after_sequence_number);
}

// Tests the reverse tabbing behavior of the keyboard navigation.
TEST_F(CaptureModeTest, ReverseTabbingTest) {
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* event_generator = GetEventGenerator();
  for (CaptureModeSource source :
       {CaptureModeSource::kFullscreen, CaptureModeSource::kRegion,
        CaptureModeSource::kWindow}) {
    auto* controller = StartCaptureSession(source, CaptureModeType::kImage);
    CaptureModeSessionTestApi test_api(controller->capture_mode_session());
    // Nothing is focused initially.
    EXPECT_EQ(FocusGroup::kNone, test_api.GetCurrentFocusGroup());
    EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
    // Reverse tabbing once and the focus should be on the close button.
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
    EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
    EXPECT_TRUE(
        CaptureModeSessionFocusCycler::HighlightHelper::Get(GetCloseButton())
            ->has_focus());
    controller->Stop();
  }
}

// A regression test for a UAF issue reported at https://crbug.com/1350743, in
// which if a the native widget of the settings menu gets deleted without
// calling `Close()` or `CloseNow()` on the widget, we get a UAF. This can
// happen when all the windows in the window tree hierarchy gets deleted e.g.
// when shutting down.
TEST_F(CaptureModeTest, SettingsMenuWidgetDestruction) {
  CaptureModeTestApi().StartForFullscreen(true);
  ClickOnView(GetSettingsButton(), GetEventGenerator());
  auto* widget = GetCaptureModeSettingsWidget();
  ASSERT_TRUE(widget);
  delete widget->GetNativeWindow();
}

// A test class that uses a mock time task environment.
class CaptureModeMockTimeTest : public CaptureModeTest {
 public:
  CaptureModeMockTimeTest()
      : CaptureModeTest(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  CaptureModeMockTimeTest(const CaptureModeMockTimeTest&) = delete;
  CaptureModeMockTimeTest& operator=(const CaptureModeMockTimeTest&) = delete;
  ~CaptureModeMockTimeTest() override = default;
};

// Tests that the consecutive screenshots histogram is recorded properly.
TEST_F(CaptureModeMockTimeTest, ConsecutiveScreenshotsHistograms) {
  constexpr char kConsecutiveScreenshotsHistogram[] =
      "Ash.CaptureModeController.ConsecutiveScreenshots";
  base::HistogramTester histogram_tester;

  auto take_n_screenshots = [this](int n) {
    for (int i = 0; i < n; ++i) {
      auto* controller = StartImageRegionCapture();
      controller->PerformCapture();
    }
  };

  // Take three consecutive screenshots. Should only record after 5 seconds.
  StartImageRegionCapture();
  const gfx::Rect capture_region(200, 200, 400, 400);
  SelectRegion(capture_region);
  take_n_screenshots(3);
  histogram_tester.ExpectBucketCount(kConsecutiveScreenshotsHistogram, 3, 0);
  task_environment()->FastForwardBy(base::Seconds(5));
  histogram_tester.ExpectBucketCount(kConsecutiveScreenshotsHistogram, 3, 1);

  // Take only one screenshot. This should not be recorded.
  take_n_screenshots(1);
  histogram_tester.ExpectBucketCount(kConsecutiveScreenshotsHistogram, 1, 0);
  task_environment()->FastForwardBy(base::Seconds(5));
  histogram_tester.ExpectBucketCount(kConsecutiveScreenshotsHistogram, 1, 0);

  // Take a screenshot, change source and take another screenshot. This should
  // count as 2 consecutive screenshots.
  take_n_screenshots(1);
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kImage);
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(kConsecutiveScreenshotsHistogram, 2, 0);
  task_environment()->FastForwardBy(base::Seconds(5));
  histogram_tester.ExpectBucketCount(kConsecutiveScreenshotsHistogram, 2, 1);
}

// Tests that the user capture region will be cleared up after a period of time.
TEST_F(CaptureModeMockTimeTest, ClearUserCaptureRegionBetweenSessions) {
  UpdateDisplay("900x800");
  auto* controller = StartImageRegionCapture();
  EXPECT_EQ(gfx::Rect(), controller->user_capture_region());

  const gfx::Rect capture_region(100, 100, 600, 700);
  SelectRegion(capture_region);
  EXPECT_EQ(capture_region, controller->user_capture_region());
  controller->PerformCapture();
  EXPECT_EQ(capture_region, controller->user_capture_region());

  // Start region image capture again shortly after the previous capture
  // session, we should still be able to reuse the previous capture region.
  task_environment()->FastForwardBy(base::Minutes(1));
  StartImageRegionCapture();
  EXPECT_EQ(capture_region, controller->user_capture_region());
  auto* event_generator = GetEventGenerator();
  // Even if the capture is cancelled, we still remember the capture region.
  SendKey(ui::VKEY_ESCAPE, event_generator);
  EXPECT_EQ(capture_region, controller->user_capture_region());

  // Wait for 8 second and then start region image capture again. We should have
  // forgot the previous capture region.
  task_environment()->FastForwardBy(base::Minutes(8));
  StartImageRegionCapture();
  EXPECT_EQ(gfx::Rect(), controller->user_capture_region());
}

// Tests that in Region mode, the capture bar hides and shows itself correctly.
TEST_F(CaptureModeTest, CaptureBarOpacity) {
  UpdateDisplay("800x700");

  auto* event_generator = GetEventGenerator();
  auto* controller = StartImageRegionCapture();
  EXPECT_TRUE(controller->IsActive());

  ui::Layer* capture_bar_layer = GetCaptureModeBarWidget()->GetLayer();

  // Check to see it starts off opaque.
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());

  // Make sure that the bar is transparent when selecting a region.
  const gfx::Rect target_region(gfx::BoundingRect(
      gfx::Point(0, 0),
      GetCaptureModeBarView()->GetBoundsInScreen().top_right() +
          gfx::Vector2d(0, -50)));
  event_generator->MoveMouseTo(target_region.origin());
  event_generator->PressLeftButton();
  EXPECT_EQ(0.f, capture_bar_layer->GetTargetOpacity());
  event_generator->MoveMouseTo(target_region.bottom_right());
  EXPECT_EQ(0.f, capture_bar_layer->GetTargetOpacity());
  event_generator->ReleaseLeftButton();

  // When there is no overlap of the selected region and the bar, the bar should
  // be opaque.
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());

  // Bar becomes transparent when the region is being moved.
  event_generator->MoveMouseTo(target_region.origin() + gfx::Vector2d(50, 50));
  event_generator->PressLeftButton();
  EXPECT_EQ(0.f, capture_bar_layer->GetTargetOpacity());
  event_generator->MoveMouseTo(target_region.bottom_center());
  EXPECT_EQ(0.f, capture_bar_layer->GetTargetOpacity());
  event_generator->ReleaseLeftButton();

  // The region overlaps the capture bar, so we set the opacity of the bar to
  // the overlapped opacity.
  EXPECT_EQ(capture_mode::kCaptureUiOverlapOpacity,
            capture_bar_layer->GetTargetOpacity());

  // When there is overlap, the toolbar turns opaque on mouseover.
  event_generator->MoveMouseTo(
      GetCaptureModeBarView()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());

  // Capture bar drops back to the overlapped opacity when the mouse is no
  // longer hovering.
  event_generator->MoveMouseTo(
      GetCaptureModeBarView()->GetBoundsInScreen().top_center() +
      gfx::Vector2d(0, -50));
  EXPECT_EQ(capture_mode::kCaptureUiOverlapOpacity,
            capture_bar_layer->GetTargetOpacity());

  // Check that the opacity is reset when we select another region.
  SelectRegion(target_region);
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());
}

TEST_F(CaptureModeTest, CaptureBarOpacityOnHoveringOnCaptureLabel) {
  UpdateDisplay("800x700");

  auto* event_generator = GetEventGenerator();
  auto* controller = StartImageRegionCapture();
  EXPECT_TRUE(controller->IsActive());
  ui::Layer* capture_bar_layer = GetCaptureModeBarWidget()->GetLayer();

  // Set the capture region to make it overlap with the capture bar. And then
  // move the mouse to the outside of the capture bar, verify it has the
  // overlapped opacity.
  const gfx::Rect capture_region(200, 500, 130, 130);
  SelectRegion(capture_region);
  event_generator->MoveMouseTo({10, 10});
  EXPECT_EQ(capture_mode::kCaptureUiOverlapOpacity,
            capture_bar_layer->GetTargetOpacity());

  // Move mouse on top of the capture label, verify the bar becomes fully
  // opaque.
  event_generator->MoveMouseTo(
      GetCaptureModeLabelWidget()->GetWindowBoundsInScreen().CenterPoint());
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());
}

// Tests that the quick action histogram is recorded properly.
TEST_F(CaptureModeTest, QuickActionHistograms) {
  constexpr char kQuickActionHistogramName[] =
      "Ash.CaptureModeController.QuickAction";
  base::HistogramTester histogram_tester;

  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kImage);
  EXPECT_TRUE(controller->IsActive());
  {
    CaptureNotificationWaiter waiter;
    controller->PerformCapture();
    waiter.Wait();
  }
  // Verify clicking delete on screenshot notification.
  base::RunLoop loop;
  SetUpFileDeletionVerifier(&loop);
  const int delete_button = 1;
  ClickOnNotification(delete_button);
  loop.Run();
  EXPECT_FALSE(GetPreviewNotification());
  histogram_tester.ExpectBucketCount(kQuickActionHistogramName,
                                     CaptureQuickAction::kDelete, 1);

  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                   CaptureModeType::kImage);
  {
    CaptureNotificationWaiter waiter;
    controller->PerformCapture();
    waiter.Wait();
  }
  // Click on the notification body. This should open the default handler.
  ClickOnNotification(std::nullopt);
  EXPECT_FALSE(GetPreviewNotification());
  histogram_tester.ExpectBucketCount(kQuickActionHistogramName,
                                     CaptureQuickAction::kOpenDefault, 1);

  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                   CaptureModeType::kImage);

  {
    CaptureNotificationWaiter waiter;
    controller->PerformCapture();
    waiter.Wait();
  }
  const int edit_button = 0;
  // Verify clicking edit on screenshot notification.
  ClickOnNotification(edit_button);
  EXPECT_FALSE(GetPreviewNotification());
  histogram_tester.ExpectBucketCount(kQuickActionHistogramName,
                                     CaptureQuickAction::kBacklight, 1);
}

TEST_F(CaptureModeTest, NotificationButtonOfVideoRecording) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  CaptureModeTestApi test_api;
  test_api.FlushRecordingServiceForTesting();
  test_api.StopVideoRecording();
  CaptureNotificationWaiter().Wait();
  EXPECT_TRUE(GetPreviewNotification());

  // Verify clicking delete on video recording notification.
  base::RunLoop loop;
  SetUpFileDeletionVerifier(&loop);
  const int delete_button = 0;
  ClickOnNotification(delete_button);
  loop.Run();
  EXPECT_FALSE(GetPreviewNotification());
}

TEST_F(CaptureModeTest, CannotDoMultipleRecordings) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);

  auto* controller = CaptureModeController::Get();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());
  EXPECT_EQ(CaptureModeType::kVideo, controller->type());

  // Start a new session with the current type which set to kVideo, the type
  // should be switched automatically to kImage, and video toggle button should
  // be disabled.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(controller->IsActive());
  EXPECT_EQ(CaptureModeType::kImage, controller->type());
  EXPECT_TRUE(GetImageToggleButton()->selected());
  EXPECT_FALSE(GetVideoToggleButton()->selected());
  EXPECT_FALSE(GetVideoToggleButton()->GetEnabled());

  // Clicking on the video button should do nothing.
  ClickOnView(GetVideoToggleButton(), GetEventGenerator());
  EXPECT_TRUE(GetImageToggleButton()->selected());
  EXPECT_FALSE(GetVideoToggleButton()->selected());
  EXPECT_EQ(CaptureModeType::kImage, controller->type());

  // Things should go back to normal when there's no recording going on and the
  // video file has been fully saved.
  controller->Stop();
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  EXPECT_FALSE(controller->can_start_new_recording());
  WaitForCaptureFileToBeSaved();
  EXPECT_TRUE(controller->can_start_new_recording());

  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  EXPECT_EQ(CaptureModeType::kVideo, controller->type());
  EXPECT_FALSE(GetImageToggleButton()->selected());
  EXPECT_TRUE(GetVideoToggleButton()->selected());
  EXPECT_TRUE(GetVideoToggleButton()->GetEnabled());
}

// Tests the basic settings menu functionality.
TEST_F(CaptureModeTest, SettingsMenuVisibilityBasic) {
  auto* event_generator = GetEventGenerator();
  auto* controller = StartImageRegionCapture();
  EXPECT_TRUE(controller->IsActive());

  // Session starts with settings menu not initialized.
  EXPECT_FALSE(GetCaptureModeSettingsWidget());

  // Test clicking the settings button toggles the button as well as
  // opens/closes the settings menu.
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_TRUE(GetCaptureModeSettingsWidget());
  EXPECT_TRUE(GetSettingsButton()->toggled());
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_FALSE(GetCaptureModeSettingsWidget());
  EXPECT_FALSE(GetSettingsButton()->toggled());
}

// Tests how interacting with the rest of the screen (i.e. clicking outside of
// the bar/menu, on other buttons) affects whether the settings menu should
// close or not.
TEST_F(CaptureModeTest, SettingsMenuVisibilityClicking) {
  UpdateDisplay("800x700");

  auto* event_generator = GetEventGenerator();
  auto* controller = StartImageRegionCapture();
  EXPECT_TRUE(controller->IsActive());

  // Test clicking on the option of settings menu doesn't close the
  // settings menu.
  ClickOnView(GetSettingsButton(), event_generator);
  ClickOnView(GetCaptureModeSettingsView(), event_generator);
  EXPECT_TRUE(GetCaptureModeSettingsWidget());
  EXPECT_TRUE(GetSettingsButton()->toggled());
  CaptureModeSettingsTestApi test_api;
  ClickOnView(test_api.GetAudioOffOption(), event_generator);
  EXPECT_TRUE(GetCaptureModeSettingsWidget());
  EXPECT_TRUE(GetSettingsButton()->toggled());

  // Test clicking on the capture bar closes the settings menu.
  event_generator->MoveMouseTo(
      GetCaptureModeBarView()->GetBoundsInScreen().top_center() +
      gfx::Vector2d(0, 2));
  event_generator->ClickLeftButton();
  EXPECT_FALSE(GetCaptureModeSettingsWidget());
  EXPECT_FALSE(GetSettingsButton()->toggled());

  // Test clicking on a different source closes the settings menu.
  ClickOnView(GetSettingsButton(), event_generator);
  ClickOnView(GetFullscreenToggleButton(), event_generator);
  EXPECT_FALSE(GetCaptureModeSettingsWidget());

  // Test clicking on a different type closes the settings menu.
  ClickOnView(GetSettingsButton(), event_generator);
  ClickOnView(GetVideoToggleButton(), event_generator);
  EXPECT_FALSE(GetCaptureModeSettingsWidget());

  // Exit the capture session with the settings menu open, and test to make sure
  // the new session starts with the settings menu hidden.
  ClickOnView(GetSettingsButton(), event_generator);
  SendKey(ui::VKEY_ESCAPE, event_generator);
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kImage);
  EXPECT_FALSE(GetCaptureModeSettingsWidget());

  // Take a screenshot with the settings menu open, and test to make sure the
  // new session starts with the settings menu hidden.
  ClickOnView(GetSettingsButton(), event_generator);
  // Take screenshot.
  SendKey(ui::VKEY_RETURN, event_generator);
  StartImageRegionCapture();
  EXPECT_FALSE(GetCaptureModeSettingsWidget());
}

// Tests capture bar and settings menu visibility / opacity when capture region
// is being or after drawn.
TEST_F(CaptureModeTest, CaptureBarAndSettingsMenuVisibilityDrawingRegion) {
  UpdateDisplay("800x700");

  auto* event_generator = GetEventGenerator();
  auto* controller = StartImageRegionCapture();
  auto* capture_bar_widget = GetCaptureModeBarWidget();
  ui::Layer* capture_bar_layer = capture_bar_widget->GetLayer();
  EXPECT_TRUE(controller->IsActive());
  auto* session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(session->session_type(), SessionType::kReal);

  // Test the settings menu and capture bar are hidden when the user clicks to
  // start selecting a region.
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_TRUE(GetCaptureModeSettingsWidget());
  const gfx::Rect target_region(gfx::BoundingRect(
      gfx::Point(0, 0),
      capture_bar_widget->GetWindowBoundsInScreen().top_right() +
          gfx::Vector2d(0, -50)));
  // Moving the cursor outside the bounds of the settings menu should update the
  // cursor to `kPointer`, since the only possible operation here when clicking
  // is to dismiss the settings menu rather than take a screenshot or update the
  // region.
  event_generator->MoveMouseTo(target_region.origin());
  auto* cursor_manager = Shell::Get()->cursor_manager();
  EXPECT_EQ(CursorType::kPointer, cursor_manager->GetCursor().type());

  // Pressing outside the bounds of the settings should dismiss it immediately,
  // update the cursor to `kCell` (to signal that it's now possible to select a
  // region), but region selection doesn't start until the next click event.
  event_generator->PressLeftButton();
  EXPECT_FALSE(GetCaptureModeSettingsWidget());
  EXPECT_EQ(CursorType::kCell, cursor_manager->GetCursor().type());
  EXPECT_FALSE(session->is_selecting_region());
  event_generator->ReleaseLeftButton();
  EXPECT_FALSE(session->is_selecting_region());

  event_generator->PressLeftButton();
  EXPECT_TRUE(session->is_selecting_region());
  event_generator->MoveMouseTo(target_region.bottom_right());
  EXPECT_EQ(0.f, capture_bar_layer->GetTargetOpacity());
  event_generator->ReleaseLeftButton();
  EXPECT_FALSE(GetCaptureModeSettingsWidget());

  // Test that the settings menu will dismiss immediately when clicking
  // somewhere in the middle of the capture region.
  ClickOnView(GetSettingsButton(), event_generator);
  event_generator->MoveMouseTo(target_region.origin() + gfx::Vector2d(50, 50));
  event_generator->PressLeftButton();
  EXPECT_FALSE(GetCaptureModeSettingsWidget());
  event_generator->ReleaseLeftButton();
  event_generator->PressLeftButton();
  EXPECT_EQ(CursorType::kMove, cursor_manager->GetCursor().type());
  EXPECT_FALSE(session->is_selecting_region());
  EXPECT_TRUE(session->is_drag_in_progress());
  // This creates a region that overlaps with the capture bar. The capture bar
  // should be fully transparent while dragging the region is in progress.
  event_generator->MoveMouseTo(target_region.bottom_center());
  EXPECT_EQ(0.f, capture_bar_layer->GetTargetOpacity());
  event_generator->ReleaseLeftButton();

  // With an overlapping region (as dragged to above), the capture bar opacity
  // is changed based on hover. If the settings menu is open/visible, the
  // capture bar will always be visible no matter if the mouse is hovered on it
  // or not.
  event_generator->MoveMouseTo(target_region.origin());
  EXPECT_EQ(capture_mode::kCaptureUiOverlapOpacity,
            capture_bar_layer->GetTargetOpacity());
  // Move mouse on top of the capture bar, verify that capture bar becomes
  // visible.
  event_generator->MoveMouseTo(
      capture_bar_widget->GetWindowBoundsInScreen().CenterPoint());
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_TRUE(GetCaptureModeSettingsWidget());
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());

  // Move mouse onto the settings menu, confirm the capture bar is still
  // visible.
  event_generator->MoveMouseTo(
      GetCaptureModeSettingsView()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());

  // Move mouse to the outside of the capture bar and settings, verify that
  // settings menu are still open and both capture bar and settings have full
  // opaque.
  event_generator->MoveMouseTo(target_region.origin());
  auto* settings_menu = GetCaptureModeSettingsView();
  EXPECT_TRUE(settings_menu);
  EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());
  EXPECT_EQ(1.f, settings_menu->layer()->GetTargetOpacity());

  // Close settings menu, and move mouse to the outside of the capture bar,
  // verify capture bar has the overlapped opacity.
  ClickOnView(GetSettingsButton(), event_generator);
  event_generator->MoveMouseTo(target_region.origin());
  EXPECT_EQ(capture_mode::kCaptureUiOverlapOpacity,
            capture_bar_layer->GetTargetOpacity());
}

TEST_F(CaptureModeTest, CaptureFolderSetting) {
  auto* controller = CaptureModeController::Get();
  auto* test_delegate = controller->delegate_for_testing();
  const auto default_downloads_folder =
      test_delegate->GetUserDefaultDownloadsFolder();

  auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, default_downloads_folder);
  EXPECT_TRUE(capture_folder.is_default_downloads_folder);

  const base::FilePath custom_folder(FILE_PATH_LITERAL("/home/tests"));
  controller->SetCustomCaptureFolder(custom_folder);

  capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, custom_folder);
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);
}

TEST_F(CaptureModeTest, CaptureFolderSetToDefaultDownloads) {
  auto* controller = CaptureModeController::Get();
  auto* test_delegate = controller->delegate_for_testing();

  const base::FilePath custom_folder(FILE_PATH_LITERAL("/home/tests"));
  controller->SetCustomCaptureFolder(custom_folder);
  auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);

  // If the user selects the default downloads folder manually, we should be
  // able to detect that.
  const auto default_downloads_folder =
      test_delegate->GetUserDefaultDownloadsFolder();
  controller->SetCustomCaptureFolder(default_downloads_folder);

  capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, default_downloads_folder);
  EXPECT_TRUE(capture_folder.is_default_downloads_folder);
}

TEST_F(CaptureModeTest, UsesDefaultFolderWithCustomFolderSet) {
  auto* controller = CaptureModeController::Get();
  auto* test_delegate = controller->delegate_for_testing();

  const base::FilePath custom_folder(FILE_PATH_LITERAL("/home/tests"));
  controller->SetCustomCaptureFolder(custom_folder);
  auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);

  // If the user selects to force use the default downloads folder even while
  // a custom folder is set, we should respect that, but we shouldn't clear the
  // custom folder.
  controller->SetUsesDefaultCaptureFolder(true);
  const auto default_downloads_folder =
      test_delegate->GetUserDefaultDownloadsFolder();
  capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, default_downloads_folder);
  EXPECT_TRUE(capture_folder.is_default_downloads_folder);

  // Setting another custom folder value, would reset the
  // "UsesDefaultCaptureFolder" value, and the new custom folder will be used.
  const base::FilePath custom_folder2(FILE_PATH_LITERAL("/home/tests2"));
  controller->SetCustomCaptureFolder(custom_folder2);
  capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, custom_folder2);
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);
}

TEST_F(CaptureModeTest, CaptureFolderSetToEmptyPath) {
  auto* controller = CaptureModeController::Get();
  auto* test_delegate = controller->delegate_for_testing();

  const base::FilePath custom_folder(FILE_PATH_LITERAL("/home/tests"));
  controller->SetCustomCaptureFolder(custom_folder);
  auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);

  // If we set the custom path to an empty folder to clear, we should revert
  // back to the default downloads folder.
  controller->SetCustomCaptureFolder(base::FilePath());

  const auto default_downloads_folder =
      test_delegate->GetUserDefaultDownloadsFolder();
  capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, default_downloads_folder);
  EXPECT_TRUE(capture_folder.is_default_downloads_folder);
}

TEST_F(CaptureModeTest, SimulateUserCancelingDlpWarningDialog) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // Simulate the user canceling the DLP warning dialog at the end of the
  // recording which is shown to the user to alert about restricted content
  // showing up on the screen during the recording. In this case, the user
  // requests the deletion of the file.
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  test_delegate->set_should_save_after_dlp_check(false);
  base::RunLoop loop;
  SetUpFileDeletionVerifier(&loop);
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  loop.Run();
  // No notification should show in this case, nor any thing on Tote.
  EXPECT_FALSE(GetPreviewNotification());
  ash::HoldingSpaceTestApi holding_space_api;
  EXPECT_TRUE(holding_space_api.GetScreenCaptureViews().empty());
  EXPECT_TRUE(controller->can_start_new_recording());
}

// Tests that `CaptureScreenshotOfGivenWindow` can take window screenshot
// successfully and that the image size matches the window size.
TEST_F(CaptureModeTest, InstantScreenshotForkWindow) {
  const gfx::Rect window_bounds(10, 20, 700, 500);
  std::unique_ptr<aura::Window> window(CreateTestWindow(window_bounds));
  CaptureModeController::Get()->CaptureScreenshotOfGivenWindow(window.get());
  const auto file_path = WaitForCaptureFileToBeSaved();
  gfx::Image image = ReadAndDecodeImageFile(file_path);
  EXPECT_EQ(image.Size(), window_bounds.size());
}

// Tests the capture mode behavior in the default capture mode session and
// during video recording.
TEST_F(CaptureModeTest, CaptureModeDefaultBehavior) {
  CaptureModeController* controller = StartCaptureSession(
      CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  ASSERT_TRUE(controller->IsActive());
  auto* session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(session->session_type(), SessionType::kReal);
  const CaptureModeBehavior* active_behavior = session->active_behavior();
  ASSERT_TRUE(active_behavior);

  auto expected_behavior = [&]() {
    EXPECT_TRUE(active_behavior->ShouldImageCaptureTypeBeAllowed());
    EXPECT_TRUE(active_behavior->ShouldVideoCaptureTypeBeAllowed());
    EXPECT_TRUE(active_behavior->ShouldFulscreenCaptureSourceBeAllowed());
    EXPECT_TRUE(active_behavior->ShouldRegionCaptureSourceBeAllowed());
    EXPECT_TRUE(active_behavior->ShouldWindowCaptureSourceBeAllowed());
    EXPECT_TRUE(
        active_behavior->SupportsAudioRecordingMode(AudioRecordingMode::kOff));
    EXPECT_TRUE(active_behavior->SupportsAudioRecordingMode(
        AudioRecordingMode::kMicrophone));
    EXPECT_TRUE(active_behavior->ShouldCameraSelectionSettingsBeIncluded());
    EXPECT_TRUE(active_behavior->ShouldDemoToolsSettingsBeIncluded());
    EXPECT_TRUE(active_behavior->ShouldSaveToSettingsBeIncluded());
    EXPECT_TRUE(active_behavior->ShouldGifBeSupported());
    EXPECT_TRUE(active_behavior->ShouldShowPreviewNotification());
    EXPECT_FALSE(active_behavior->ShouldSkipVideoRecordingCountDown());
    EXPECT_FALSE(active_behavior->ShouldCreateAnnotationsOverlayController());
    EXPECT_TRUE(active_behavior->ShouldShowUserNudge());
    EXPECT_FALSE(active_behavior->ShouldAutoSelectFirstCamera());
  };

  expected_behavior();
  views::Widget* bar_widget = GetCaptureModeBarWidget();
  ASSERT_TRUE(bar_widget);

  EXPECT_TRUE(GetImageToggleButton());
  EXPECT_TRUE(GetVideoToggleButton());
  EXPECT_TRUE(GetFullscreenToggleButton());
  EXPECT_TRUE(GetRegionToggleButton());
  EXPECT_TRUE(GetWindowToggleButton());
  EXPECT_FALSE(GetStartRecordingButton());
  EXPECT_TRUE(GetSettingsButton());
  EXPECT_TRUE(GetCloseButton());

  StartVideoRecordingImmediately();
  expected_behavior();
}

// Tests that the capture mode session can be started with the keyboard shortcut
// 'Ctrl + Shift + Overview' with `kImage` as the default type and `kRegion` as
// the default source. And the screen recording can be ended with the keyboard
// shortcut 'Search + Shift + X'.
TEST_F(CaptureModeTest, KeyboardShortcutTest) {
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kKeyboardShortcut, 0);

  auto* event_generator = GetEventGenerator();
  event_generator->PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
                                      ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
  auto* controller = CaptureModeController::Get();
  EXPECT_TRUE(controller->IsActive());
  EXPECT_EQ(controller->type(), CaptureModeType::kImage);
  EXPECT_EQ(controller->source(), CaptureModeSource::kRegion);
  controller->SetType(CaptureModeType::kVideo);
  controller->SetSource(CaptureModeSource::kFullscreen);

  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  event_generator->PressAndReleaseKey(ui::VKEY_X,
                                      ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
  EXPECT_FALSE(controller->is_recording_in_progress());
  histogram_tester.ExpectBucketCount(
      kEndRecordingReasonInClamshellHistogramName,
      EndRecordingReason::kKeyboardShortcut, 1);
}

namespace {

// -----------------------------------------------------------------------------
// TestVideoCaptureOverlay:

// Defines a fake video capture overlay to be used in testing the behavior of
// the cursor overlay. The VideoRecordingWatcher will control this overlay via
// mojo.
using Overlay = viz::mojom::FrameSinkVideoCaptureOverlay;
class TestVideoCaptureOverlay : public Overlay {
 public:
  explicit TestVideoCaptureOverlay(mojo::PendingReceiver<Overlay> receiver)
      : receiver_(this, std::move(receiver)) {}
  ~TestVideoCaptureOverlay() override = default;

  const gfx::RectF& last_bounds() const { return last_bounds_; }

  bool IsHidden() const { return last_bounds_ == gfx::RectF(); }

  // viz::mojom::FrameSinkVideoCaptureOverlay:
  void SetImageAndBounds(const SkBitmap& image,
                         const gfx::RectF& bounds) override {
    last_bounds_ = bounds;
  }
  void SetBounds(const gfx::RectF& bounds) override { last_bounds_ = bounds; }
  void OnCapturedMouseEvent(const gfx::Point& coordinates) override {}

 private:
  mojo::Receiver<viz::mojom::FrameSinkVideoCaptureOverlay> receiver_;
  gfx::RectF last_bounds_;
};

// -----------------------------------------------------------------------------
//  CaptureModeCursorOverlayTest:

// Defines a test fixure to test the behavior of the cursor overlay.
class CaptureModeCursorOverlayTest : public CaptureModeTest {
 public:
  CaptureModeCursorOverlayTest() = default;
  ~CaptureModeCursorOverlayTest() override = default;

  aura::Window* window() const { return window_.get(); }
  TestVideoCaptureOverlay* fake_overlay() const { return fake_overlay_.get(); }

  // CaptureModeTest:
  void SetUp() override {
    CaptureModeTest::SetUp();
    window_ = CreateTestWindow(gfx::Rect(200, 200));
  }

  void TearDown() override {
    window_.reset();
    CaptureModeTest::TearDown();
  }

  CaptureModeController* StartRecordingAndSetupFakeOverlay(
      CaptureModeSource source) {
    auto* controller = StartCaptureSession(source, CaptureModeType::kVideo);
    auto* event_generator = GetEventGenerator();
    if (source == CaptureModeSource::kWindow)
      event_generator->MoveMouseToCenterOf(window_.get());
    StartVideoRecordingImmediately();
    EXPECT_TRUE(controller->is_recording_in_progress());
    auto* recording_watcher = controller->video_recording_watcher_for_testing();
    mojo::PendingRemote<Overlay> overlay_pending_remote;
    fake_overlay_ = std::make_unique<TestVideoCaptureOverlay>(
        overlay_pending_remote.InitWithNewPipeAndPassReceiver());
    recording_watcher->BindCursorOverlayForTesting(
        std::move(overlay_pending_remote));

    // The overlay should be initially hidden until a mourse event is received.
    FlushOverlay();
    EXPECT_TRUE(fake_overlay()->IsHidden());

    // Generating some mouse events may or may not show the overlay, depending
    // on the conditions of the test. Each test will verify its expectation
    // after this returns.
    event_generator->MoveMouseBy(10, 10);
    FlushOverlay();

    return controller;
  }

  void FlushOverlay() {
    auto* controller = CaptureModeController::Get();
    DCHECK(controller->is_recording_in_progress());
    controller->video_recording_watcher_for_testing()
        ->FlushCursorOverlayForTesting();
  }

  // The docked magnifier is one of the features that force the software-
  // composited cursor to be used when enabled. We use it to test the behavior
  // of the cursor overlay in that case.
  void SetDockedMagnifierEnabled(bool enabled) {
    Shell::Get()->docked_magnifier_controller()->SetEnabled(enabled);
  }

  // Checks that capturing a screenshot hides the cursor. After the capture is
  // complete, checks that the cursor returns to the previous state, i.e.
  // hidden for tablet mode but visible for clamshell mode.
  void CaptureScreenshotAndCheckCursorVisibility(
      CaptureModeController* controller) {
    EXPECT_EQ(controller->type(), CaptureModeType::kImage);

    auto* cursor_manager = Shell::Get()->cursor_manager();
    bool in_tablet_mode = display::Screen::GetScreen()->InTabletMode();

    // The capture mode session locks the cursor for the whole active session
    // except in the tablet mode unless the cursor is visible.
    EXPECT_EQ(!in_tablet_mode, cursor_manager->IsCursorLocked());
    EXPECT_EQ(!in_tablet_mode, cursor_manager->IsCursorVisible());
    EXPECT_TRUE(controller->IsActive());

    // Make sure the cursor is hidden while capturing the screenshot.
    CaptureNotificationWaiter waiter;
    controller->PerformCapture();
    EXPECT_FALSE(cursor_manager->IsCursorVisible());
    EXPECT_FALSE(controller->IsActive());

    // The cursor visibility should be restored after the capture is done.
    waiter.Wait();
    EXPECT_EQ(!in_tablet_mode, cursor_manager->IsCursorVisible());
    EXPECT_FALSE(cursor_manager->IsCursorLocked());
  }

 private:
  std::unique_ptr<aura::Window> window_;
  std::unique_ptr<TestVideoCaptureOverlay> fake_overlay_;
};

}  // namespace

TEST_F(CaptureModeCursorOverlayTest, TabletModeHidesCursorOverlay) {
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kFullscreen);
  EXPECT_FALSE(fake_overlay()->IsHidden());

  // Entering tablet mode should hide the cursor overlay.
  SwitchToTabletMode();
  FlushOverlay();
  EXPECT_TRUE(fake_overlay()->IsHidden());

  // Exiting tablet mode should reshow the overlay.
  LeaveTabletMode();
  FlushOverlay();
  EXPECT_FALSE(fake_overlay()->IsHidden());
}

// Tests that the cursor is hidden while taking a screenshot in tablet mode and
// remains hidden afterward.
TEST_F(CaptureModeCursorOverlayTest, TabletModeHidesCursor) {
  // Enter tablet mode.
  SwitchToTabletMode();

  auto* cursor_manager = Shell::Get()->cursor_manager();
  CaptureModeController* controller = StartCaptureSession(
      CaptureModeSource::kFullscreen, CaptureModeType::kImage);

  // Test the hardware cursor.
  CaptureScreenshotAndCheckCursorVisibility(controller);

  // Test the software cursor enabled by docked magnifier.
  SetDockedMagnifierEnabled(true);
  EXPECT_TRUE(IsCursorCompositingEnabled());
  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                   CaptureModeType::kImage);
  CaptureScreenshotAndCheckCursorVisibility(controller);

  // Exiting tablet mode.
  LeaveTabletMode();
  EXPECT_TRUE(cursor_manager->IsCursorVisible());
}

// Tests that a cursor is hidden while taking a fullscreen screenshot
// (crbug.com/1186652).
TEST_F(CaptureModeCursorOverlayTest, CursorInFullscreenScreenshot) {
  auto* cursor_manager = Shell::Get()->cursor_manager();
  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  CaptureModeController* controller = StartCaptureSession(
      CaptureModeSource::kFullscreen, CaptureModeType::kImage);
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(175, 175));

  // Test the hardware cursor.
  CaptureScreenshotAndCheckCursorVisibility(controller);

  // Test the software cursor enabled by docked magnifier.
  SetDockedMagnifierEnabled(true);
  EXPECT_TRUE(IsCursorCompositingEnabled());
  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                   CaptureModeType::kImage);
  CaptureScreenshotAndCheckCursorVisibility(controller);
}

// Tests that a cursor is hidden while taking a region screenshot
// (crbug.com/1186652).
TEST_F(CaptureModeCursorOverlayTest, CursorInPartialRegionScreenshot) {
  // Use a set display size as we will be choosing points in this test.
  UpdateDisplay("800x700");

  auto* cursor_manager = Shell::Get()->cursor_manager();
  EXPECT_FALSE(cursor_manager->IsCursorLocked());
  auto* event_generator = GetEventGenerator();
  auto* controller = StartImageRegionCapture();

  // Create the initial capture region.
  const gfx::Rect target_region(gfx::Rect(50, 50, 200, 200));
  SelectRegion(target_region);
  event_generator->MoveMouseTo(gfx::Point(175, 175));

  // Test the hardware cursor.
  CaptureScreenshotAndCheckCursorVisibility(controller);

  // Test the software cursor enabled by docked magnifier.
  SetDockedMagnifierEnabled(true);
  EXPECT_TRUE(IsCursorCompositingEnabled());
  controller = StartImageRegionCapture();
  CaptureScreenshotAndCheckCursorVisibility(controller);
}

TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInitiallyEnabled) {
  // The software cursor is enabled before recording starts.
  SetDockedMagnifierEnabled(true);
  EXPECT_TRUE(IsCursorCompositingEnabled());

  // Hence the overlay will be hidden initially.
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kFullscreen);
  EXPECT_TRUE(fake_overlay()->IsHidden());
}

TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInFullscreenRecording) {
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kFullscreen);
  EXPECT_FALSE(fake_overlay()->IsHidden());

  // When the software-composited cursor is enabled, the overlay is hidden to
  // avoid having two overlapping cursors in the video.
  SetDockedMagnifierEnabled(true);
  EXPECT_TRUE(IsCursorCompositingEnabled());
  FlushOverlay();
  EXPECT_TRUE(fake_overlay()->IsHidden());

  SetDockedMagnifierEnabled(false);
  EXPECT_FALSE(IsCursorCompositingEnabled());
  FlushOverlay();
  EXPECT_FALSE(fake_overlay()->IsHidden());
}

TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInPartialRegionRecording) {
  CaptureModeController::Get()->SetUserCaptureRegion(gfx::Rect(20, 20),
                                                     /*by_user=*/true);
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kRegion);
  EXPECT_FALSE(fake_overlay()->IsHidden());

  // The behavior in this case is exactly the same as in fullscreen recording.
  SetDockedMagnifierEnabled(true);
  EXPECT_TRUE(IsCursorCompositingEnabled());
  FlushOverlay();
  EXPECT_TRUE(fake_overlay()->IsHidden());
}

TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInWindowRecording) {
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kWindow);
  EXPECT_FALSE(fake_overlay()->IsHidden());

  // When recording a window, the software cursor has no effect of the cursor
  // overlay, since the cursor widget is not in the recorded window subtree, so
  // it cannot be captured by the frame sink capturer. We have to provide cursor
  // capturing through the overlay.
  SetDockedMagnifierEnabled(true);
  EXPECT_TRUE(IsCursorCompositingEnabled());
  FlushOverlay();
  EXPECT_FALSE(fake_overlay()->IsHidden());
}

TEST_F(CaptureModeCursorOverlayTest, OverlayHidesWhenOutOfBounds) {
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kWindow);
  EXPECT_FALSE(fake_overlay()->IsHidden());

  const gfx::Point bottom_right =
      window()->GetBoundsInRootWindow().bottom_right();
  auto* generator = GetEventGenerator();
  // Generate a click event to overcome throttling.
  generator->MoveMouseTo(bottom_right);
  generator->ClickLeftButton();
  FlushOverlay();
  EXPECT_TRUE(fake_overlay()->IsHidden());
}

namespace {

// A CursorShapeClient that always fails to return cursor data.
class FakeCursorShapeClient : public aura::client::CursorShapeClient {
 public:
  FakeCursorShapeClient() = default;
  FakeCursorShapeClient(const FakeCursorShapeClient&) = delete;
  FakeCursorShapeClient& operator=(const FakeCursorShapeClient&) = delete;
  ~FakeCursorShapeClient() override = default;

  // aura::client::CursorShapeClient:
  std::optional<ui::CursorData> GetCursorData(
      const ui::Cursor& cursor) const override {
    return std::nullopt;
  }
};

}  // namespace

TEST_F(CaptureModeCursorOverlayTest, OverlayWhenCursorIsHiddenOrFails) {
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kWindow);
  EXPECT_FALSE(fake_overlay()->IsHidden());

  // Move cursor, the overlay should update.
  gfx::RectF last_bounds = fake_overlay()->last_bounds();
  auto* generator = GetEventGenerator();
  // Generate a click event to overcome throttling.
  generator->MoveMouseBy(10, 10);
  generator->ClickLeftButton();
  FlushOverlay();
  EXPECT_FALSE(fake_overlay()->IsHidden());
  EXPECT_NE(fake_overlay()->last_bounds(), last_bounds);

  // Hide cursor, the overlay should be empty and hidden.
  auto* cursor_manager = Shell::Get()->cursor_manager();
  cursor_manager->SetCursor(CursorType::kNone);
  // Lock the cursor to prevent mouse events from changing it back.
  cursor_manager->LockCursor();
  generator->MoveMouseBy(10, 10);
  generator->ClickLeftButton();
  FlushOverlay();
  EXPECT_TRUE(fake_overlay()->IsHidden());
  EXPECT_EQ(fake_overlay()->last_bounds(), gfx::RectF());

  // While the cursor is hidden, the overlay shouldn't change.
  generator->MoveMouseBy(10, 10);
  generator->ClickLeftButton();
  FlushOverlay();
  EXPECT_TRUE(fake_overlay()->IsHidden());
  EXPECT_EQ(fake_overlay()->last_bounds(), gfx::RectF());

  // Unhide cursor, the overlay should update.
  cursor_manager->UnlockCursor();
  generator->ClickLeftButton();
  FlushOverlay();
  EXPECT_FALSE(fake_overlay()->IsHidden());
  EXPECT_NE(fake_overlay()->last_bounds(), gfx::RectF());

  // Set a fake cursor shape client so that retrieving the cursor data fails.
  // The overlay shouldn't change.
  FakeCursorShapeClient cursor_shape_client;
  aura::client::SetCursorShapeClient(&cursor_shape_client);
  last_bounds = fake_overlay()->last_bounds();
  generator->MoveMouseBy(10, 10);
  generator->ClickLeftButton();
  FlushOverlay();
  EXPECT_FALSE(fake_overlay()->IsHidden());
  EXPECT_EQ(fake_overlay()->last_bounds(), last_bounds);
}

// Verifies that the cursor overlay bounds calculation takes into account the
// cursor image scale factor. https://crbug.com/1222494.
TEST_F(CaptureModeCursorOverlayTest, OverlayBoundsAccountForCursorScaleFactor) {
  UpdateDisplay("500x400");
  StartRecordingAndSetupFakeOverlay(CaptureModeSource::kFullscreen);
  EXPECT_FALSE(fake_overlay()->IsHidden());

  auto* cursor_manager = Shell::Get()->cursor_manager();
  auto set_cursor = [cursor_manager](const gfx::Size& cursor_image_size,
                                     float cursor_image_scale_factor) {
    SkBitmap cursor_image;
    cursor_image.allocN32Pixels(cursor_image_size.width(),
                                cursor_image_size.height());
    ui::Cursor cursor = ui::Cursor::NewCustom(
        std::move(cursor_image), gfx::Point(), cursor_image_scale_factor);
    cursor.SetPlatformCursor(
        ui::CursorFactory::GetInstance()->CreateImageCursor(
            cursor.type(), cursor.custom_bitmap(), cursor.custom_hotspot(),
            cursor.image_scale_factor()));
    cursor_manager->SetCursor(std::move(cursor));
  };

  struct {
    gfx::Size cursor_size;
    float cursor_image_scale_factor;
  } kTestCases[] = {
      {
          gfx::Size(50, 50),
          /*cursor_image_scale_factor=*/2.f,
      },
      {
          gfx::Size(25, 25),
          /*cursor_image_scale_factor=*/1.f,
      },
  };

  // Both of the above test cases should yield the same cursor overlay relative
  // bounds when the cursor is at the center of the screen.
  // Origin is 0.5f (center)
  // Size is 25 (cursor image dip size) / {500,400} = {0.05f, 0.0625f}
  const gfx::RectF expected_overlay_bounds{0.5f, 0.5f, 0.05f, 0.0625f};

  const gfx::Point screen_center =
      window()->GetRootWindow()->bounds().CenterPoint();
  auto* generator = GetEventGenerator();

  for (const auto& test_case : kTestCases) {
    set_cursor(test_case.cursor_size, test_case.cursor_image_scale_factor);
    // Lock the cursor to prevent mouse events from changing it back to a
    // default kPointer cursor type.
    cursor_manager->LockCursor();

    // Generate a click event to overcome throttling.
    generator->MoveMouseTo(screen_center);
    generator->ClickLeftButton();
    FlushOverlay();
    EXPECT_FALSE(fake_overlay()->IsHidden());
    EXPECT_EQ(expected_overlay_bounds, fake_overlay()->last_bounds());

    // Unlock the cursor back.
    cursor_manager->UnlockCursor();
  }
}

// -----------------------------------------------------------------------------
// TODO(afakhry): Add more cursor overlay tests.

// Test fixture to verify capture mode + projector integration.

namespace {

constexpr char kProjectorCreationFlowHistogramName[] =
    "Ash.Projector.CreationFlow.ClamshellMode";

}  // namespace

class ProjectorCaptureModeIntegrationTests
    : public CaptureModeTest,
      public ::testing::WithParamInterface<CaptureModeSource> {
 public:
  ProjectorCaptureModeIntegrationTests() = default;
  ~ProjectorCaptureModeIntegrationTests() override = default;

  static constexpr gfx::Rect kUserRegion{20, 50, 60, 70};

  MockProjectorClient* projector_client() {
    return projector_helper_.projector_client();
  }
  aura::Window* window() const { return window_.get(); }

  // CaptureModeTest:
  void SetUp() override {
    CaptureModeTest::SetUp();
    projector_helper_.SetUp();
    window_ = CreateTestWindow(gfx::Rect(20, 30, 200, 200));
    CaptureModeController::Get()->SetUserCaptureRegion(kUserRegion,
                                                       /*by_user=*/true);
  }

  void TearDown() override {
    window_.reset();
    CaptureModeTest::TearDown();
  }

  void StartProjectorModeSession() {
    projector_helper_.StartProjectorModeSession();
  }

  void StartRecordingForProjectorFromSource(CaptureModeSource source) {
    StartProjectorModeSession();
    auto* controller = CaptureModeController::Get();
    controller->SetSource(source);

    switch (source) {
      case CaptureModeSource::kFullscreen:
      case CaptureModeSource::kRegion:
        break;
      case CaptureModeSource::kWindow:
        auto* generator = GetEventGenerator();
        generator->MoveMouseTo(window_->GetBoundsInScreen().CenterPoint());
        break;
    }
    CaptureModeTestApi().PerformCapture();
    WaitForRecordingToStart();
    EXPECT_TRUE(controller->is_recording_in_progress());
  }

 protected:
  ProjectorCaptureModeIntegrationHelper projector_helper_;
  std::unique_ptr<aura::Window> window_;
  base::HistogramTester histogram_tester_;
};

// static
constexpr gfx::Rect ProjectorCaptureModeIntegrationTests::kUserRegion;

TEST_F(ProjectorCaptureModeIntegrationTests, EntryPoint) {
  // With the most recent source type set to kImage, starting capture mode for
  // the projector workflow will still force it to kVideo.
  auto* controller = CaptureModeController::Get();
  controller->SetType(CaptureModeType::kImage);
  // Also, audio recording is initially disabled. However, the projector flow
  // forces it enabled.
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());

  StartProjectorModeSession();
  EXPECT_TRUE(controller->IsActive());
  auto* session = controller->capture_mode_session();
  ASSERT_TRUE(session);
  const CaptureModeBehavior* behavior = session->active_behavior();
  ASSERT_TRUE(behavior);
  EXPECT_TRUE(
      behavior->SupportsAudioRecordingMode(AudioRecordingMode::kMicrophone));
  EXPECT_EQ(AudioRecordingMode::kMicrophone,
            controller->GetEffectiveAudioRecordingMode());

  constexpr char kEntryPointHistogram[] =
      "Ash.CaptureModeController.EntryPoint.ClamshellMode";
  histogram_tester_.ExpectBucketCount(kEntryPointHistogram,
                                      CaptureModeEntryType::kProjector, 1);
}

// Tests that a fullscreen screenshot can be taken via the keyboard shortcut
// while a Projector-initiated session is active without ending the session.
TEST_P(ProjectorCaptureModeIntegrationTests, FullscreenScreenshotKeyCombo) {
  StartProjectorModeSession();
  PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN);
  WaitForCaptureFileToBeSaved();
  auto* controller = CaptureModeController::Get();
  ASSERT_TRUE(controller->IsActive());
  CaptureModeBehavior* active_behavior =
      controller->capture_mode_session()->active_behavior();
  ASSERT_TRUE(active_behavior);
  EXPECT_EQ(active_behavior->behavior_type(), BehaviorType::kProjector);
}

// Tests that the settings view is simplified in projector mode.
TEST_F(ProjectorCaptureModeIntegrationTests, CaptureModeSettings) {
  auto* controller = CaptureModeController::Get();
  StartProjectorModeSession();
  auto* event_generator = GetEventGenerator();

  ClickOnView(GetSettingsButton(), event_generator);

  CaptureModeSettingsTestApi test_api;

  // The "Save-to" menu group should never be added.
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_FALSE(save_to_menu_group);

  // The audio-off option should never be added.
  EXPECT_FALSE(test_api.GetAudioOffOption());

  CaptureModeMenuGroup* audio_input_menu_group =
      test_api.GetAudioInputMenuGroup();
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioMicrophone));
  EXPECT_EQ(AudioRecordingMode::kMicrophone,
            controller->GetEffectiveAudioRecordingMode());
}

TEST_F(ProjectorCaptureModeIntegrationTests, AudioCaptureDisabledByPolicy) {
  auto* controller = CaptureModeController::Get();
  auto* delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  delegate->set_is_audio_capture_disabled_by_policy(true);
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());

  // A projector session is not allowed to start when audio recording is
  // disabled by policy.
  EXPECT_FALSE(projector_helper_.CanStartProjectorSession());
}

TEST_F(ProjectorCaptureModeIntegrationTests,
       AudioCaptureDisabledByPolicyAfterSessionStarts) {
  auto* controller = CaptureModeController::Get();
  auto* delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());

  // At this point, a Projector session is allowed to begin.
  EXPECT_TRUE(projector_helper_.CanStartProjectorSession());
  StartProjectorModeSession();

  // Flip the audio policy now before recording begins. Attempt to start
  // recording, but expect that the capture mode session will end *without*
  // starting a new recording.
  delegate->set_is_audio_capture_disabled_by_policy(true);
  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForSessionToEnd();
  EXPECT_FALSE(controller->is_recording_in_progress());

  // The Projector session preconditions should now be up-to-date.
  EXPECT_FALSE(projector_helper_.CanStartProjectorSession());
}

// Tests the keyboard navigation for projector mode. The `image_toggle_button_`
// in `CaptureModeTypeView` and the `Off` audio input option in
// `CaptureModeSettingsView` are not available in projector mode.
TEST_F(ProjectorCaptureModeIntegrationTests, KeyboardNavigationBasic) {
  auto* controller = CaptureModeController::Get();
  // Use `kFullscreen` here to minimize the number of tabs to reach the setting
  // button.
  controller->SetSource(CaptureModeSource::kFullscreen);
  StartProjectorModeSession();

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  EXPECT_FALSE(GetImageToggleButton());
  // Tab once, check the current focused view is the video toggle button.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(test_api.GetCurrentFocusedView()->GetView(),
            GetVideoToggleButton());

  // Now tab four times to focus the settings button and enter space to open the
  // settings menu.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/4);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(FocusGroup::kPendingSettings, test_api.GetCurrentFocusGroup());
  CaptureModeSettingsView* settings_menu =
      test_api.GetCaptureModeSettingsView();
  ASSERT_TRUE(settings_menu);

  CaptureModeSettingsTestApi settings_test_api;
  // The `Off` option should not be visible.
  EXPECT_FALSE(settings_test_api.GetAudioOffOption());
  // Tab twice, the current focused view is the `Microphone` option.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/2);
  EXPECT_EQ(test_api.GetCurrentFocusedView()->GetView(),
            settings_test_api.GetMicrophoneOption());
}

TEST_F(ProjectorCaptureModeIntegrationTests, BarButtonsState) {
  auto* controller = CaptureModeController::Get();
  StartProjectorModeSession();
  EXPECT_TRUE(controller->IsActive());

  // The image toggle button shouldn't be available, whereas the video toggle
  // button should be enabled and active.
  EXPECT_FALSE(GetImageToggleButton());
  EXPECT_TRUE(GetVideoToggleButton()->GetEnabled());
  EXPECT_TRUE(GetVideoToggleButton()->selected());
}

TEST_F(ProjectorCaptureModeIntegrationTests, StartEndRecording) {
  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kFullscreen);
  StartProjectorModeSession();
  EXPECT_TRUE(controller->IsActive());
  histogram_tester_.ExpectUniqueSample(kProjectorCreationFlowHistogramName,
                                       ProjectorCreationFlow::kSessionStarted,
                                       /*expected_bucket_count=*/1);

  // Hit Enter to begin recording. The recording session should be marked for
  // projector.
  PressAndReleaseKey(ui::VKEY_RETURN);
  EXPECT_CALL(*projector_client(), StartSpeechRecognition());
  WaitForRecordingToStart();
  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kRecordingStarted,
                                      /*expected_count=*/1);

  EXPECT_FALSE(controller->IsActive());
  EXPECT_TRUE(controller->is_recording_in_progress());
  const CaptureModeBehavior* active_behavior =
      controller->video_recording_watcher_for_testing()->active_behavior();
  ASSERT_TRUE(active_behavior);
  EXPECT_TRUE(active_behavior->ShouldCreateAnnotationsOverlayController());

  EXPECT_CALL(*projector_client(), StopSpeechRecognition());
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  WaitForCaptureFileToBeSaved();

  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kRecordingEnded,
                                      /*expected_count=*/1);
  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kSessionStopped,
                                      /*expected_count=*/1);
  histogram_tester_.ExpectTotalCount(kProjectorCreationFlowHistogramName,
                                     /*expected_count=*/4);
}

TEST_F(ProjectorCaptureModeIntegrationTests,
       ProjectorSessionNeverStartsWhenCaptureModeIsBlocked) {
  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kFullscreen);

  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  test_delegate->set_is_allowed_by_policy(false);
  ProjectorController::Get()->StartProjectorSession(
      base::SafeBaseName::Create("projector_data").value());

  // Both sessions will never start.
  EXPECT_FALSE(controller->IsActive());
  EXPECT_FALSE(ProjectorSession::Get()->is_active());
  EXPECT_FALSE(controller->is_recording_in_progress());
}

TEST_F(ProjectorCaptureModeIntegrationTests,
       ProjectorSessionNeverStartsWhenVideoRecordingIsOnGoing) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  EXPECT_CALL(
      *projector_client(),
      OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
          NewScreencastPreconditionState::kDisabled,
          {NewScreencastPreconditionReason::kScreenRecordingInProgress})));
  controller->StartVideoRecordingImmediatelyForTesting();

  EXPECT_TRUE(controller->is_recording_in_progress());
  EXPECT_FALSE(ProjectorSession::Get()->is_active());
  EXPECT_NE(ProjectorController::Get()->GetNewScreencastPrecondition().state,
            NewScreencastPreconditionState::kEnabled);
  // There is another OnNewScreencastPreconditionChanged() call during tear
  // down.
  EXPECT_CALL(*projector_client(),
              OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
                  NewScreencastPreconditionState::kEnabled,
                  {NewScreencastPreconditionReason::kEnabledBySoda})));
}

// Tests that the capture mode configurations in normal capture mode session
// that include the capture mode type, capture mode source and capture mode
// audio settings will not be overridden by the projector-initiated capture mode
// session.
TEST_F(ProjectorCaptureModeIntegrationTests,
       RestoreCaptureSessionConfigurationsInNormalCaptureSession) {
  // Start an image capture mode session in window mode.
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);

  // Stop the normal capture mode session and start a new projector-initated
  // capture mode session. By default the session will be of type video and in
  // fullscreen mode with audio on.
  controller->Stop();
  StartProjectorModeSession();
  EXPECT_TRUE(controller->IsActive());
  EXPECT_EQ(controller->type(), CaptureModeType::kVideo);
  EXPECT_EQ(controller->source(), CaptureModeSource::kFullscreen);
  EXPECT_EQ(AudioRecordingMode::kMicrophone,
            controller->GetEffectiveAudioRecordingMode());

  // Stop the projector-initiated capture mode session and the original capture
  // mode configurations will be restored.
  controller->Stop();
  EXPECT_EQ(controller->type(), CaptureModeType::kImage);
  EXPECT_EQ(controller->source(), CaptureModeSource::kWindow);
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());

  // Start a new projector-initiated capture mode session and start the region
  // recording.
  StartProjectorModeSession();
  controller->SetSource(CaptureModeSource::kRegion);
  CaptureModeTestApi test_api;
  test_api.SetUserSelectedRegion(gfx::Rect(100, 100, 200, 200));
  test_api.PerformCapture();
  WaitForSeconds(1);

  // Start another capture mode session and the source should be restored as
  // what has been set before the projector-initiated capture mode session.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_EQ(controller->source(), CaptureModeSource::kWindow);
  controller->Stop();
  test_api.StopVideoRecording();

  // After completing the video recording in projector-initiated capture mode
  // session, the capture mode configurations will be restored as what has been
  // set before the projector-initiated capture mode session.
  EXPECT_EQ(controller->type(), CaptureModeType::kImage);
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());
}

namespace {

enum AbortReason {
  kBlockedByDlp,
  kBlockedByPolicy,
  kUserPressedEsc,
};

struct {
  const std::string scope_trace;
  const AbortReason reason;
} kTestCases[] = {
    {"Blocked by DLP", kBlockedByDlp},
    {"Blocked by policy", kBlockedByPolicy},
    {"User Pressed Esc", kUserPressedEsc},
};

}  // namespace

TEST_F(ProjectorCaptureModeIntegrationTests,
       ProjectorSessionAbortedBeforeCountDownStarts) {
  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kFullscreen);

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.scope_trace);
    StartProjectorModeSession();
    auto* test_delegate = static_cast<TestCaptureModeDelegate*>(
        controller->delegate_for_testing());

    switch (test_case.reason) {
      case kBlockedByDlp:
        test_delegate->set_is_allowed_by_dlp(false);
        PressAndReleaseKey(ui::VKEY_RETURN);
        break;
      case kBlockedByPolicy:
        test_delegate->set_is_allowed_by_policy(false);
        PressAndReleaseKey(ui::VKEY_RETURN);
        break;
      case kUserPressedEsc:
        PressAndReleaseKey(ui::VKEY_ESCAPE);
        break;
    }

    // The session will end immediately without a count down.
    EXPECT_FALSE(controller->IsActive());
    EXPECT_FALSE(ProjectorSession::Get()->is_active());
    EXPECT_FALSE(controller->is_recording_in_progress());

    // Prepare for next iteration by resetting things back to default.
    test_delegate->ResetAllowancesToDefault();
  }
  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kSessionStarted,
                                      /*expected_count=*/3);
  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kRecordingAborted,
                                      /*expected_count=*/3);
  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kSessionStopped,
                                      /*expected_count=*/3);
  histogram_tester_.ExpectTotalCount(kProjectorCreationFlowHistogramName,
                                     /*expected_count=*/9);
}

TEST_F(ProjectorCaptureModeIntegrationTests,
       ProjectorSessionAbortedAfterCountDownStarts) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kFullscreen);

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.scope_trace);
    StartProjectorModeSession();
    PressAndReleaseKey(ui::VKEY_RETURN);
    auto* test_delegate = static_cast<TestCaptureModeDelegate*>(
        controller->delegate_for_testing());

    switch (test_case.reason) {
      case kBlockedByDlp:
        test_delegate->set_is_allowed_by_dlp(false);
        break;
      case kBlockedByPolicy:
        test_delegate->set_is_allowed_by_policy(false);
        break;
      case kUserPressedEsc:
        PressAndReleaseKey(ui::VKEY_ESCAPE);
        break;
    }

    WaitForSessionToEnd();
    EXPECT_FALSE(ProjectorSession::Get()->is_active());
    EXPECT_FALSE(controller->is_recording_in_progress());

    // Prepare for next iteration by resetting things back to default.
    test_delegate->ResetAllowancesToDefault();
  }

  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kSessionStarted,
                                      /*expected_count=*/3);
  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kRecordingAborted,
                                      /*expected_count=*/3);
  histogram_tester_.ExpectBucketCount(kProjectorCreationFlowHistogramName,
                                      ProjectorCreationFlow::kSessionStopped,
                                      /*expected_count=*/3);
  histogram_tester_.ExpectTotalCount(kProjectorCreationFlowHistogramName,
                                     /*expected_count=*/9);
}

TEST_F(ProjectorCaptureModeIntegrationTests, AnnotationsOverlayWidget) {
  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kFullscreen);
  StartProjectorModeSession();
  EXPECT_TRUE(controller->IsActive());

  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForRecordingToStart();
  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();
  EXPECT_FALSE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/false);

  auto* annotator_controller = Shell::Get()->annotator_controller();
  annotator_controller->EnableAnnotatorTool();
  EXPECT_TRUE(overlay_controller->is_enabled());
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/true);

  annotator_controller->ResetTools();
  EXPECT_FALSE(overlay_controller->is_enabled());
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/false);
}

TEST_F(ProjectorCaptureModeIntegrationTests,
       AnnotationsOverlayDockedMagnifier) {
  auto* controller = CaptureModeController::Get();
  controller->SetSource(CaptureModeSource::kFullscreen);
  StartProjectorModeSession();
  EXPECT_TRUE(controller->IsActive());

  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForRecordingToStart();
  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();

  auto* annotator_controller = Shell::Get()->annotator_controller();
  annotator_controller->EnableAnnotatorTool();
  EXPECT_TRUE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();

  // Before the docked magnifier gets enabled, the overlay's bounds should match
  // the root window's bounds.
  auto* root_window = overlay_window->GetRootWindow();
  const gfx::Rect root_window_bounds = root_window->bounds();
  EXPECT_EQ(root_window_bounds, overlay_window->GetBoundsInRootWindow());

  // Once the magnifier is enabled, the overlay should be pushed down so that
  // it doesn't cover the magnifier viewport.
  auto* docked_magnifier = Shell::Get()->docked_magnifier_controller();
  docked_magnifier->SetEnabled(true);
  const gfx::Rect expected_bounds = gfx::SubtractRects(
      root_window_bounds,
      docked_magnifier->GetTotalMagnifierBoundsForRoot(root_window));
  EXPECT_EQ(expected_bounds, overlay_window->GetBoundsInRootWindow());

  // It should go back to original bounds once the magnifier is disabled.
  docked_magnifier->SetEnabled(false);
  EXPECT_EQ(root_window_bounds, overlay_window->GetBoundsInRootWindow());
}

TEST_P(ProjectorCaptureModeIntegrationTests, AnnotationsOverlayWidgetBounds) {
  const auto capture_source = GetParam();
  StartRecordingForProjectorFromSource(capture_source);
  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();
  EXPECT_FALSE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();
  VerifyOverlayWindow(overlay_window, capture_source, kUserRegion);
}

// Regression test for https://crbug.com/1322655.
TEST_P(ProjectorCaptureModeIntegrationTests,
       AnnotationsOverlayWidgetBoundsSecondDisplay) {
  UpdateDisplay("800x700,801+0-800x700");
  const gfx::Point point_in_second_display = gfx::Point(1000, 500);
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(point_in_second_display);
  window()->SetBoundsInScreen(
      gfx::Rect(900, 0, 600, 500),
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          Shell::GetAllRootWindows()[1]));

  const auto capture_source = GetParam();
  StartRecordingForProjectorFromSource(capture_source);
  const auto roots = Shell::GetAllRootWindows();
  EXPECT_EQ(roots[1], GetWindowBeingRecorded()->GetRootWindow());

  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();
  EXPECT_FALSE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();
  VerifyOverlayWindow(overlay_window, capture_source, kUserRegion);
}

// Tests the projector behavior in the projector-initiated capture mode session
// and during video recording.
TEST_P(ProjectorCaptureModeIntegrationTests, ProjectorBehavior) {
  CaptureModeController* controller = CaptureModeController::Get();
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());
  EXPECT_TRUE(projector_helper_.CanStartProjectorSession());
  StartProjectorModeSession();
  ASSERT_TRUE(controller->IsActive());
  auto* session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_TRUE(session);
  ASSERT_EQ(session->session_type(), SessionType::kReal);
  const CaptureModeBehavior* projector_active_behavior =
      session->active_behavior();
  ASSERT_TRUE(projector_active_behavior);

  auto expected_behavior = [&]() {
    EXPECT_FALSE(projector_active_behavior->ShouldImageCaptureTypeBeAllowed());
    EXPECT_TRUE(projector_active_behavior->ShouldVideoCaptureTypeBeAllowed());
    EXPECT_TRUE(
        projector_active_behavior->ShouldFulscreenCaptureSourceBeAllowed());
    EXPECT_TRUE(
        projector_active_behavior->ShouldRegionCaptureSourceBeAllowed());
    EXPECT_TRUE(
        projector_active_behavior->ShouldWindowCaptureSourceBeAllowed());
    EXPECT_FALSE(projector_active_behavior->SupportsAudioRecordingMode(
        AudioRecordingMode::kOff));
    EXPECT_TRUE(projector_active_behavior->SupportsAudioRecordingMode(
        AudioRecordingMode::kMicrophone));
    EXPECT_TRUE(
        projector_active_behavior->ShouldCameraSelectionSettingsBeIncluded());
    EXPECT_TRUE(projector_active_behavior->ShouldDemoToolsSettingsBeIncluded());
    EXPECT_FALSE(projector_active_behavior->ShouldSaveToSettingsBeIncluded());
    EXPECT_FALSE(projector_active_behavior->ShouldGifBeSupported());
    EXPECT_FALSE(projector_active_behavior->ShouldShowPreviewNotification());
    EXPECT_FALSE(
        projector_active_behavior->ShouldSkipVideoRecordingCountDown());
    EXPECT_TRUE(
        projector_active_behavior->ShouldCreateAnnotationsOverlayController());
    EXPECT_FALSE(projector_active_behavior->ShouldShowUserNudge());
    EXPECT_TRUE(projector_active_behavior->ShouldAutoSelectFirstCamera());
  };

  expected_behavior();
  views::Widget* bar_widget = GetCaptureModeBarWidget();
  ASSERT_TRUE(bar_widget);

  EXPECT_FALSE(GetImageToggleButton());
  EXPECT_TRUE(GetVideoToggleButton());
  EXPECT_TRUE(GetFullscreenToggleButton());
  EXPECT_TRUE(GetRegionToggleButton());
  EXPECT_TRUE(GetWindowToggleButton());
  EXPECT_FALSE(GetStartRecordingButton());
  EXPECT_TRUE(GetSettingsButton());
  EXPECT_TRUE(GetCloseButton());

  auto* annotator_controller = Shell::Get()->annotator_controller();
  annotator_controller->EnableAnnotatorTool();
  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForRecordingToStart();
  expected_behavior();
  CaptureModeTestApi().StopVideoRecording();
}

// Tests that neither preview notification nor recording in tote is shown if in
// projector mode.
TEST_P(ProjectorCaptureModeIntegrationTests,
       NotShowRecordingInToteOrNotificationForProjectorMode) {
  const auto capture_source = GetParam();
  StartRecordingForProjectorFromSource(capture_source);
  CaptureModeTestApi().StopVideoRecording();
  WaitForCaptureFileToBeSaved();
  EXPECT_FALSE(GetPreviewNotification());
  ash::HoldingSpaceTestApi holding_space_api;
  EXPECT_TRUE(holding_space_api.GetScreenCaptureViews().empty());
}

// Tests that metrics are recorded correctly for capture configuration entering
// from projector in both clamshell and tablet mode.
TEST_P(ProjectorCaptureModeIntegrationTests,
       ProjectorCaptureConfigurationMetrics) {
  const auto capture_source = GetParam();
  constexpr char kProjectorCaptureConfigurationHistogramBase[] =
      "CaptureConfiguration";
  ash::CaptureModeTestApi test_api;

  const bool kTabletEnabledStates[]{false, true};

  for (const bool tablet_enabled : kTabletEnabledStates) {
    if (tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    const std::string histogram_name =
        BuildHistogramName(kProjectorCaptureConfigurationHistogramBase,
                           test_api.GetBehavior(BehaviorType::kProjector),
                           /*append_ui_mode_suffix=*/true);
    histogram_tester_.ExpectBucketCount(
        histogram_name,
        GetConfiguration(CaptureModeType::kVideo, capture_source,
                         RecordingType::kWebM),
        0);

    StartRecordingForProjectorFromSource(capture_source);
    WaitForSeconds(1);
    test_api.StopVideoRecording();
    EXPECT_FALSE(CaptureModeController::Get()->is_recording_in_progress());

    histogram_tester_.ExpectUniqueSample(
        histogram_name,
        GetConfiguration(CaptureModeType::kVideo, capture_source,
                         RecordingType::kWebM),
        1);

    WaitForCaptureFileToBeSaved();
  }
}

// Tests that metrics are recorded correctly for screen recording length
// entering from projector in both clamshell and tablet mode.
TEST_P(ProjectorCaptureModeIntegrationTests,
       ProjectorScreenRecordingLengthMetrics) {
  const auto capture_source = GetParam();
  constexpr char kProjectorRecordTimeHistogramBase[] = "ScreenRecordingLength";
  ash::CaptureModeTestApi test_api;

  const bool kTabletEnabledStates[]{false, true};

  for (const bool tablet_enabled : kTabletEnabledStates) {
    if (tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    StartRecordingForProjectorFromSource(capture_source);
    WaitForSeconds(1);
    test_api.StopVideoRecording();
    EXPECT_FALSE(CaptureModeController::Get()->is_recording_in_progress());

    WaitForCaptureFileToBeSaved();

    histogram_tester_.ExpectUniqueSample(
        BuildHistogramName(kProjectorRecordTimeHistogramBase,
                           test_api.GetBehavior(BehaviorType::kProjector),
                           /*append_ui_mode_suffix=*/true),
        /*sample=*/1, /*expected_bucket_count=*/1);
  }
}

// Tests that metrics are recorded correctly for capture region adjustment
// entering from projector in both clamshell and tablet mode.
TEST_F(ProjectorCaptureModeIntegrationTests,
       ProjectorCaptureRegionAdjustmentTest) {
  constexpr char kProjectorCaptureRegionAdjustmentHistogramBase[] =
      "CaptureRegionAdjusted";

  auto resize_and_reset_region = [](ui::test::EventGenerator* event_generator,
                                    const gfx::Point& top_right) {
    // Enlarges the region and then resize it back to its original size.
    event_generator->set_current_screen_location(top_right);
    event_generator->DragMouseTo(top_right + gfx::Vector2d(50, 50));
    event_generator->DragMouseTo(top_right);
  };

  auto move_and_reset_region = [](ui::test::EventGenerator* event_generator,
                                  const gfx::Point& drag_point) {
    // Moves the region and then moves it back to its original position.
    event_generator->set_current_screen_location(drag_point);
    event_generator->DragMouseTo(drag_point + gfx::Vector2d(-50, -50));
    event_generator->DragMouseTo(drag_point);
  };

  ash::CaptureModeTestApi test_api;
  const std::string histogram_name =
      BuildHistogramName(kProjectorCaptureRegionAdjustmentHistogramBase,
                         test_api.GetBehavior(BehaviorType::kProjector),
                         /*append_ui_mode_suffix=*/true);
  histogram_tester_.ExpectBucketCount(histogram_name, 0, 0);
  auto* event_generator = GetEventGenerator();
  const gfx::Rect target_region(gfx::Rect(100, 100, 200, 200));
  auto top_right = target_region.top_right();

  const bool kTabletEnabledStates[] = {false, true};
  for (const bool tablet_enabled : kTabletEnabledStates) {
    if (tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    StartProjectorModeSession();
    auto* controller = CaptureModeController::Get();
    controller->SetSource(CaptureModeSource::kRegion);
    test_api.SetUserSelectedRegion(target_region);

    // Resize the region twice by dragging the top right of the region out and
    // then back again.
    resize_and_reset_region(event_generator, top_right);

    // Move the region twice by dragging within the region.
    const gfx::Point drag_point(300, 300);
    move_and_reset_region(event_generator, drag_point);

    test_api.PerformCapture();
    WaitForSeconds(1);
    test_api.StopVideoRecording();
    EXPECT_FALSE(controller->is_recording_in_progress());

    histogram_tester_.ExpectBucketCount(histogram_name, 4, 1);

    WaitForCaptureFileToBeSaved();
  }
}

INSTANTIATE_TEST_SUITE_P(All,
                         ProjectorCaptureModeIntegrationTests,
                         testing::Values(CaptureModeSource::kFullscreen,
                                         CaptureModeSource::kRegion,
                                         CaptureModeSource::kWindow));

class AnnotatorCaptureModeIntegrationTests
    : public CaptureModeTest,
      public ::testing::WithParamInterface<CaptureModeSource> {
 public:
  AnnotatorCaptureModeIntegrationTests() = default;
  ~AnnotatorCaptureModeIntegrationTests() override = default;

  static constexpr gfx::Rect kUserRegion{20, 50, 60, 70};

  aura::Window* window() const { return window_.get(); }

  // CaptureModeTest:
  void SetUp() override {
    CaptureModeTest::SetUp();
    annotator_helper_.SetUp();
    window_ = CreateTestWindow(gfx::Rect(20, 30, 200, 200));
    CaptureModeController::Get()->SetUserCaptureRegion(kUserRegion,
                                                       /*by_user=*/true);
  }

  void TearDown() override {
    window_.reset();
    CaptureModeTest::TearDown();
  }

  void StartRecordingFromSource(CaptureModeSource source) {
    ash::CaptureModeTestApi test_api;

    switch (source) {
      case CaptureModeSource::kFullscreen:
        test_api.StartForFullscreen(/*for_video=*/true);
        break;
      case CaptureModeSource::kRegion:
        test_api.StartForRegion(/*for_video=*/true);
        break;
      case CaptureModeSource::kWindow:
        test_api.StartForWindow(/*for_video=*/true);
        auto* generator = GetEventGenerator();
        generator->MoveMouseTo(window_->GetBoundsInScreen().CenterPoint());
        break;
    }
    CaptureModeTestApi().PerformCapture();
    WaitForRecordingToStart();
    EXPECT_TRUE(CaptureModeController::Get()->is_recording_in_progress());
  }

 protected:
  AnnotatorIntegrationHelper annotator_helper_;
  std::unique_ptr<aura::Window> window_;
  base::HistogramTester histogram_tester_;
};

TEST_F(AnnotatorCaptureModeIntegrationTests, AnnotationsOverlayWidget) {
  StartRecordingFromSource(CaptureModeSource::kFullscreen);

  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForRecordingToStart();
  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();
  EXPECT_FALSE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/false);

  auto* annotator_controller = Shell::Get()->annotator_controller();
  annotator_controller->EnableAnnotatorTool();
  EXPECT_TRUE(overlay_controller->is_enabled());
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/true);

  annotator_controller->ResetTools();
  EXPECT_FALSE(overlay_controller->is_enabled());
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/false);
}

TEST_F(AnnotatorCaptureModeIntegrationTests,
       AnnotationsOverlayDockedMagnifier) {
  StartRecordingFromSource(CaptureModeSource::kFullscreen);

  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForRecordingToStart();
  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();

  auto* annotator_controller = Shell::Get()->annotator_controller();
  annotator_controller->EnableAnnotatorTool();
  EXPECT_TRUE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();

  // Before the docked magnifier gets enabled, the overlay's bounds should match
  // the root window's bounds.
  auto* root_window = overlay_window->GetRootWindow();
  const gfx::Rect root_window_bounds = root_window->bounds();
  EXPECT_EQ(root_window_bounds, overlay_window->GetBoundsInRootWindow());

  // Once the magnifier is enabled, the overlay should be pushed down so that
  // it doesn't cover the magnifier viewport.
  auto* docked_magnifier = Shell::Get()->docked_magnifier_controller();
  docked_magnifier->SetEnabled(true);
  const gfx::Rect expected_bounds = gfx::SubtractRects(
      root_window_bounds,
      docked_magnifier->GetTotalMagnifierBoundsForRoot(root_window));
  EXPECT_EQ(expected_bounds, overlay_window->GetBoundsInRootWindow());

  // It should go back to original bounds once the magnifier is disabled.
  docked_magnifier->SetEnabled(false);
  EXPECT_EQ(root_window_bounds, overlay_window->GetBoundsInRootWindow());
}

namespace {

// Defines a class that intercepts the events at the post-target handling phase
// and caches the last event target to which the event was routed.
class EventTargetCatcher : public ui::EventHandler {
 public:
  EventTargetCatcher() {
    Shell::GetPrimaryRootWindow()->AddPostTargetHandler(this);
  }
  EventTargetCatcher(const EventTargetCatcher&) = delete;
  EventTargetCatcher& operator=(const EventTargetCatcher&) = delete;
  ~EventTargetCatcher() override {
    Shell::GetPrimaryRootWindow()->RemovePostTargetHandler(this);
  }

  ui::EventTarget* last_event_target() { return last_event_target_; }

  // ui::EventHandler:
  void OnEvent(ui::Event* event) override {
    ui::EventHandler::OnEvent(event);
    last_event_target_ = event->target();
  }

 private:
  raw_ptr<ui::EventTarget> last_event_target_ = nullptr;
};

}  // namespace

TEST_F(AnnotatorCaptureModeIntegrationTests,
       AnnotationsOverlayWidgetTargeting) {
  StartRecordingFromSource(CaptureModeSource::kFullscreen);

  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForRecordingToStart();
  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();

  auto* annotator_controller = Shell::Get()->annotator_controller();
  annotator_controller->EnableAnnotatorTool();
  EXPECT_TRUE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/true);

  // Open the annotation tray bubble.
  auto* root_window = Shell::GetPrimaryRootWindow();
  auto* status_area_widget =
      RootWindowController::ForWindow(root_window)->GetStatusAreaWidget();
  AnnotationTray* annotations_tray = status_area_widget->annotation_tray();
  annotations_tray->ShowBubble();
  EXPECT_TRUE(annotations_tray->GetBubbleView());

  // Clicking anywhere outside the projector shelf pod should be targeted to the
  // overlay widget window and close the annotation tray bubble.
  EventTargetCatcher event_target_catcher;
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(gfx::Point(10, 10));
  event_generator->ClickLeftButton();
  EXPECT_EQ(overlay_window, event_target_catcher.last_event_target());
  EXPECT_FALSE(annotations_tray->GetBubbleView());

  // Now move the mouse over the projector shelf pod, the overlay should not
  // consume the event, and it should instead go through to that pod.
  EXPECT_TRUE(annotations_tray->visible_preferred());
  event_generator->MoveMouseTo(
      annotations_tray->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(annotations_tray->GetWidget()->GetNativeWindow(),
            event_target_catcher.last_event_target());

  // The overlay status hasn't changed.
  VerifyOverlayEnabledState(overlay_window, /*overlay_enabled_state=*/true);

  // Now move the mouse and then click on the stop recording button, the overlay
  // should not consume the event. The video recording should be ended.
  StopRecordingButtonTray* stop_recording_button =
      status_area_widget->stop_recording_button_tray();
  const gfx::Point stop_button_center_point =
      stop_recording_button->GetBoundsInScreen().CenterPoint();
  event_generator->MoveMouseTo(stop_button_center_point);
  event_generator->ClickLeftButton();
  EXPECT_FALSE(CaptureModeController::Get()->is_recording_in_progress());
}

// Tests that auto hidden shelf can be brought back if user moves mouse to the
// shelf activation area even while annotation is active.
TEST_F(AnnotatorCaptureModeIntegrationTests,
       BringBackAutoHiddenShelfWhileAnnotationIsOn) {
  auto* root_window = Shell::GetPrimaryRootWindow();
  // Set `shelf` to always auto-hidden.
  Shelf* shelf = RootWindowController::ForWindow(root_window)->shelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  StartRecordingFromSource(CaptureModeSource::kFullscreen);

  PressAndReleaseKey(ui::VKEY_RETURN);
  WaitForRecordingToStart();

  auto* event_generator = GetEventGenerator();
  auto* annotator_controller = Shell::Get()->annotator_controller();

  const gfx::Rect root_window_bounds_in_screen =
      root_window->GetBoundsInScreen();
  const int display_width = root_window_bounds_in_screen.width();
  const int display_height = root_window_bounds_in_screen.height();
  const gfx::Point display_center = root_window_bounds_in_screen.CenterPoint();

  struct {
    const std::string scope_trace;
    const ShelfAlignment shelf_alignment;
  } kAlignmentTestCases[] = {
      {"Shelf has botton alignment", ShelfAlignment::kBottom},
      {"Shelf has left alignment", ShelfAlignment::kLeft},
      {"Shelf has right alignment", ShelfAlignment::kRight},
  };

  for (const auto& test_case : kAlignmentTestCases) {
    SCOPED_TRACE(test_case.scope_trace);
    // Enable annotation.
    annotator_controller->EnableAnnotatorTool();

    // Verify shelf is invisible right now.
    EXPECT_FALSE(shelf->IsVisible());

    shelf->SetAlignment(test_case.shelf_alignment);
    switch (test_case.shelf_alignment) {
      case ShelfAlignment::kBottom:
      case ShelfAlignment::kBottomLocked:
        event_generator->MoveMouseTo(0, display_height);
        break;
      case ShelfAlignment::kLeft:
        event_generator->MoveMouseTo(0, display_height);
        break;
      case ShelfAlignment::kRight:
        event_generator->MoveMouseTo(display_width, display_height);
        break;
    }
    // Verify after mouse is moved on top of the shelf activation area, shelf is
    // brought back and visible once the animation to show shelf is finished.
    ShellTestApi().WaitForWindowFinishAnimating(shelf->GetWindow());
    EXPECT_TRUE(shelf->IsVisible());

    // Disable annotation.
    annotator_controller->ResetTools();
    // Move mouse to the outside of the shelf activation area, and wait for the
    // animation to hide shelf to finish.
    event_generator->MoveMouseTo(display_center);
    ShellTestApi().WaitForWindowFinishAnimating(shelf->GetWindow());
  }
}

TEST_P(AnnotatorCaptureModeIntegrationTests, AnnotationsOverlayWidgetBounds) {
  const auto capture_source = GetParam();
  StartRecordingFromSource(capture_source);
  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();
  EXPECT_FALSE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();
  VerifyOverlayWindow(overlay_window, capture_source, kUserRegion);
}

TEST_P(AnnotatorCaptureModeIntegrationTests,
       AnnotationsOverlayWidgetBoundsSecondDisplay) {
  UpdateDisplay("800x700,801+0-800x700");
  const gfx::Point point_in_second_display = gfx::Point(1000, 500);
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(point_in_second_display);
  window()->SetBoundsInScreen(
      gfx::Rect(900, 0, 600, 500),
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          Shell::GetAllRootWindows()[1]));

  const auto capture_source = GetParam();
  StartRecordingFromSource(capture_source);
  const auto roots = Shell::GetAllRootWindows();
  EXPECT_EQ(roots[1], GetWindowBeingRecorded()->GetRootWindow());

  CaptureModeTestApi test_api;
  AnnotationsOverlayController* overlay_controller =
      test_api.GetAnnotationsOverlayController();
  EXPECT_FALSE(overlay_controller->is_enabled());
  auto* overlay_window = overlay_controller->GetOverlayNativeWindow();
  VerifyOverlayWindow(overlay_window, capture_source, kUserRegion);
}

INSTANTIATE_TEST_SUITE_P(All,
                         AnnotatorCaptureModeIntegrationTests,
                         testing::Values(CaptureModeSource::kFullscreen,
                                         CaptureModeSource::kRegion,
                                         CaptureModeSource::kWindow));

// -----------------------------------------------------------------------------
// CaptureModeSettingsTest:

// Test fixture for CaptureMode settings view.
class CaptureModeSettingsTest : public CaptureModeTest {
 public:
  CaptureModeSettingsTest() = default;
  ~CaptureModeSettingsTest() override = default;

  // CaptureModeTest:
  void SetUp() override {
    CaptureModeTest::SetUp();
    FakeFolderSelectionDialogFactory::Start();
  }

  void TearDown() override {
    FakeFolderSelectionDialogFactory::Stop();
    CaptureModeTest::TearDown();
  }

  CaptureModeSettingsView* GetCaptureModeSettingsView() const {
    auto* session = CaptureModeController::Get()->capture_mode_session();
    DCHECK(session);
    return CaptureModeSessionTestApi(session).GetCaptureModeSettingsView();
  }

  void WaitForSettingsMenuToBeRefreshed() {
    base::RunLoop run_loop;
    CaptureModeSettingsTestApi().SetOnSettingsMenuRefreshedCallback(
        run_loop.QuitClosure());
    run_loop.Run();
  }
};

enum class NudgeDismissalCause {
  kPressSettingsButton,
  kCaptureViaEnterKey,
  kCaptureViaClickOnScreen,
  kCaptureViaLabelButton,
};

// Test fixture to test that various causes that lead to the dismissal of the
// user nudge, they dismiss it forever.
class CaptureModeNudgeDismissalTest
    : public CaptureModeSettingsTest,
      public ::testing::WithParamInterface<NudgeDismissalCause> {
 public:
  // Starts a session appropriate for the test param.
  CaptureModeController* StartSession() {
    switch (GetParam()) {
      case NudgeDismissalCause::kPressSettingsButton:
      case NudgeDismissalCause::kCaptureViaEnterKey:
      case NudgeDismissalCause::kCaptureViaClickOnScreen:
        return StartCaptureSession(CaptureModeSource::kFullscreen,
                                   CaptureModeType::kImage);
      case NudgeDismissalCause::kCaptureViaLabelButton:
        auto* controller = CaptureModeController::Get();
        controller->SetUserCaptureRegion(gfx::Rect(200, 300), /*by_user=*/true);
        StartCaptureSession(CaptureModeSource::kRegion,
                            CaptureModeType::kImage);
        return controller;
    }
  }

  void DoDismissalAction() {
    auto* controller = CaptureModeController::Get();
    auto* event_generator = GetEventGenerator();
    switch (GetParam()) {
      case NudgeDismissalCause::kPressSettingsButton:
        ClickOnView(GetSettingsButton(), event_generator);
        break;
      case NudgeDismissalCause::kCaptureViaEnterKey:
        PressAndReleaseKey(ui::VKEY_RETURN);
        EXPECT_FALSE(controller->IsActive());
        break;
      case NudgeDismissalCause::kCaptureViaClickOnScreen:
        event_generator->MoveMouseToCenterOf(Shell::GetPrimaryRootWindow());
        event_generator->ClickLeftButton();
        EXPECT_FALSE(controller->IsActive());
        break;
      case NudgeDismissalCause::kCaptureViaLabelButton:
        auto* label_button_widget =
            CaptureModeSessionTestApi(controller->capture_mode_session())
                .GetCaptureLabelWidget();
        EXPECT_TRUE(label_button_widget);
        ClickOnView(label_button_widget->GetContentsView(), event_generator);
        break;
    }
  }
};

TEST_P(CaptureModeNudgeDismissalTest, NudgeDismissedForever) {
  auto* controller = StartSession();
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* capture_toast_controller = capture_session->capture_toast_controller();
  auto* nudge_controller = GetUserNudgeController();
  ASSERT_TRUE(nudge_controller);
  EXPECT_TRUE(nudge_controller->is_visible());
  EXPECT_TRUE(capture_toast_controller->capture_toast_widget());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kUserNudge);

  // Trigger the action that dismisses the nudge forever, it should be removed
  // in this session (if the action doesn't stop the session) and any future
  // sessions.
  DoDismissalAction();
  if (controller->IsActive()) {
    EXPECT_FALSE(GetUserNudgeController());
    // Close the session in preparation for opening a new one.
    controller->Stop();
  }

  // Reopen a new session, the nudge should not show anymore.
  StartImageRegionCapture();
  EXPECT_FALSE(GetUserNudgeController());
}

INSTANTIATE_TEST_SUITE_P(
    All,
    CaptureModeNudgeDismissalTest,
    testing::Values(NudgeDismissalCause::kPressSettingsButton,
                    NudgeDismissalCause::kCaptureViaEnterKey,
                    NudgeDismissalCause::kCaptureViaClickOnScreen,
                    NudgeDismissalCause::kCaptureViaLabelButton));

TEST_F(CaptureModeSettingsTest, NudgeChangesRootWithBar) {
  UpdateDisplay("800x700,801+0-800x700");

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(100, 500));

  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kImage);
  auto* session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(session->session_type(), SessionType::kReal);
  auto* capture_toast_controller = session->capture_toast_controller();

  EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root());
  EXPECT_EQ(capture_toast_controller->capture_toast_widget()
                ->GetNativeWindow()
                ->GetRootWindow(),
            session->current_root());

  event_generator->MoveMouseTo(gfx::Point(1000, 500));
  EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root());
  EXPECT_EQ(capture_toast_controller->capture_toast_widget()
                ->GetNativeWindow()
                ->GetRootWindow(),
            session->current_root());
}

TEST_F(CaptureModeSettingsTest, NudgeBehaviorWhenSelectingRegion) {
  UpdateDisplay("800x700,801+0-800x700");

  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(100, 500));

  auto* controller = StartImageRegionCapture();
  auto* session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(session->session_type(), SessionType::kReal);
  EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root());

  // Nudge hides while selecting a region, but doesn't change roots until the
  // region change is committed.
  auto* nudge_controller = GetUserNudgeController();
  event_generator->MoveMouseTo(gfx::Point(1000, 500));
  event_generator->PressLeftButton();
  EXPECT_FALSE(nudge_controller->is_visible());
  event_generator->MoveMouseBy(50, 60);
  event_generator->ReleaseLeftButton();
  EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root());

  // The nudge shows again, and is on the second display.
  EXPECT_TRUE(nudge_controller->is_visible());
  EXPECT_EQ(session->capture_toast_controller()
                ->capture_toast_widget()
                ->GetNativeWindow()
                ->GetRootWindow(),
            session->current_root());
}

TEST_F(CaptureModeSettingsTest, NudgeDoesNotShowForAllUserTypes) {
  struct {
    std::string trace;
    user_manager::UserType user_type;
    bool can_see_nudge;
  } kUserTypeTestCases[] = {
      {"regular user", user_manager::UserType::kRegular, true},
      {"child", user_manager::UserType::kChild, true},
      {"guest", user_manager::UserType::kGuest, false},
      {"public account", user_manager::UserType::kPublicAccount, false},
      {"kiosk app", user_manager::UserType::kKioskApp, false},
      {"web kiosk app", user_manager::UserType::kWebKioskApp, false},
  };

  for (const auto& test_case : kUserTypeTestCases) {
    SCOPED_TRACE(test_case.trace);
    ClearLogin();
    SimulateUserLogin("[email protected]", test_case.user_type);

    auto* controller = StartImageRegionCapture();
    EXPECT_EQ(test_case.can_see_nudge, controller->CanShowUserNudge());

    auto* nudge_controller = GetUserNudgeController();
    EXPECT_EQ(test_case.can_see_nudge, !!nudge_controller);

    controller->Stop();
  }
}

// Tests that the capture mode settings menu is centered with respect to the
// capture bar.
TEST_F(CaptureModeSettingsTest, SettingsMenuCenteredWithCaptureBar) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kImage);
  auto* bar_widget = GetCaptureModeBarWidget();
  ASSERT_TRUE(bar_widget);
  ClickOnView(GetSettingsButton(), GetEventGenerator());
  auto* settings_widget = GetCaptureModeSettingsWidget();
  ASSERT_TRUE(settings_widget);
  EXPECT_NEAR(settings_widget->GetWindowBoundsInScreen().CenterPoint().x(),
              bar_widget->GetWindowBoundsInScreen().CenterPoint().x(),
              /*abs_error=*/1);
}

// Tests that it's possbile to take a screenshot using the keyboard shortcut at
// the login screen without any crashes. https://crbug.com/1266728.
TEST_F(CaptureModeSettingsTest, TakeScreenshotAtLoginScreen) {
  ClearLogin();
  PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN);
  WaitForCaptureFileToBeSaved();
}

// Tests that clicking on audio input buttons updates the state in the
// controller, and persists between sessions.
TEST_F(CaptureModeSettingsTest, AudioInputSettingsMenu) {
  auto* controller = StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();

  // Test that the audio recording preference is defaulted to off.
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());

  CaptureModeSettingsTestApi test_api;
  CaptureModeMenuGroup* audio_input_menu_group =
      test_api.GetAudioInputMenuGroup();
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioOff));
  EXPECT_FALSE(audio_input_menu_group->IsOptionChecked(kAudioMicrophone));

  // Click on the |microphone| option. It should be checked after click along
  // with |off| is unchecked. Recording preference is set to microphone.
  views::View* microphone_option = test_api.GetMicrophoneOption();
  ClickOnView(microphone_option, event_generator);
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioMicrophone));
  EXPECT_FALSE(audio_input_menu_group->IsOptionChecked(kAudioOff));
  EXPECT_EQ(AudioRecordingMode::kMicrophone,
            controller->GetEffectiveAudioRecordingMode());

  // Test that the user selected audio preference for audio recording is
  // remembered between sessions.
  SendKey(ui::VKEY_ESCAPE, event_generator);
  StartImageRegionCapture();
  EXPECT_EQ(AudioRecordingMode::kMicrophone,
            controller->GetEffectiveAudioRecordingMode());
}

TEST_F(CaptureModeSettingsTest, AudioCaptureDisabledByPolicy) {
  auto* controller = CaptureModeController::Get();

  // Even if audio recording is set to enabled, the policy setting will
  // overwrite it.
  controller->SetAudioRecordingMode(AudioRecordingMode::kMicrophone);
  auto* delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  delegate->set_is_audio_capture_disabled_by_policy(true);
  EXPECT_EQ(AudioRecordingMode::kOff,
            controller->GetEffectiveAudioRecordingMode());

  StartImageRegionCapture();

  // Open the settings menu, and check that "Audio Off" setting is dimmed out,
  // and the "Microphone" setting was not added. This menu group should be
  // marked as "managed by policy".
  ClickOnView(GetSettingsButton(), GetEventGenerator());
  CaptureModeSettingsTestApi test_api;
  CaptureModeMenuGroup* audio_input_menu_group =
      test_api.GetAudioInputMenuGroup();
  EXPECT_TRUE(audio_input_menu_group->IsManagedByPolicy());
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioOff));
  EXPECT_FALSE(audio_input_menu_group->IsOptionEnabled(kAudioOff));
  EXPECT_FALSE(test_api.GetMicrophoneOption());
}

TEST_F(CaptureModeSettingsTest, SelectFolderFromDialog) {
  auto* controller = StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);

  // Initially there should only be an option for the default downloads folder.
  CaptureModeSettingsTestApi test_api;
  EXPECT_TRUE(test_api.GetDefaultDownloadsOption());
  EXPECT_FALSE(test_api.GetCustomFolderOptionIfAny());
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));

  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
  EXPECT_FALSE(AreAllCaptureSessionUisVisible());

  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  auto* dialog_window = dialog_factory->GetDialogWindow();
  auto* window_state = WindowState::Get(dialog_window);
  ASSERT_TRUE(window_state);
  EXPECT_FALSE(window_state->CanMaximize());
  EXPECT_FALSE(window_state->CanMinimize());
  EXPECT_FALSE(window_state->CanResize());

  // Accepting the dialog with a folder selection should dismiss it and add a
  // new option for the custom selected folder in the settings menu.
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  dialog_factory->AcceptPath(custom_folder);
  WaitForSettingsMenuToBeRefreshed();
  EXPECT_FALSE(IsFolderSelectionDialogShown());
  EXPECT_TRUE(AreAllCaptureSessionUisVisible());
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  EXPECT_EQ(u"test",
            save_to_menu_group->GetOptionLabelForTesting(kCustomFolder));

  // This should update the folder that will be used by the controller.
  auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, custom_folder);
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);
}

// Tests that folder selection dialog can be opened without crash while in
// window capture mode.
TEST_F(CaptureModeSettingsTest, SelectFolderInWindowCaptureMode) {
  std::unique_ptr<aura::Window> window1(
      CreateTestWindow(gfx::Rect(0, 0, 200, 300)));
  StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);

  CaptureModeSettingsTestApi test_api;
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
}

TEST_F(CaptureModeSettingsTest, DismissDialogWithoutSelection) {
  auto* controller = StartImageRegionCapture();
  const auto old_capture_folder = controller->GetCurrentCaptureFolder();

  // Open the settings menu, and click the "Select folder" menu item.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  CaptureModeSettingsTestApi test_api;
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());

  // Cancel and dismiss the dialog. There should be no change in the folder
  // selection.
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  dialog_factory->CancelDialog();
  EXPECT_FALSE(IsFolderSelectionDialogShown());
  EXPECT_FALSE(test_api.GetCustomFolderOptionIfAny());
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));

  const auto new_capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(old_capture_folder.path, new_capture_folder.path);
  EXPECT_EQ(old_capture_folder.is_default_downloads_folder,
            new_capture_folder.is_default_downloads_folder);
}

TEST_F(CaptureModeSettingsTest, AcceptUpdatedCustomFolderFromDialog) {
  // Start a new session with a pre-configured custom folder.
  auto* controller = CaptureModeController::Get();
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  controller->SetCustomCaptureFolder(custom_folder);
  StartImageRegionCapture();

  // Open the settings menu and check there already exists an item for that
  // pre-configured custom folder.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  WaitForSettingsMenuToBeRefreshed();
  CaptureModeSettingsTestApi test_api;
  EXPECT_TRUE(test_api.GetDefaultDownloadsOption());
  auto* custom_folder_view = test_api.GetCustomFolderOptionIfAny();
  EXPECT_TRUE(custom_folder_view);
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));

  // Now open the folder selection dialog and select a different folder. The
  // existing *same* item in the menu should be updated.
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());

  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  const base::FilePath new_folder(
      CreateCustomFolderInUserDownloadsPath("test1"));
  dialog_factory->AcceptPath(new_folder);
  WaitForSettingsMenuToBeRefreshed();
  EXPECT_FALSE(IsFolderSelectionDialogShown());
  EXPECT_EQ(custom_folder_view, test_api.GetCustomFolderOptionIfAny());
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  EXPECT_EQ(u"test1",
            save_to_menu_group->GetOptionLabelForTesting(kCustomFolder));

  // This should update the folder that will be used by the controller.
  const auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, new_folder);
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);
}

TEST_F(CaptureModeSettingsTest,
       InitializeSettingsViewWithUnavailableCustomFolder) {
  // Start a new session with a pre-configured unavailable custom folder.
  auto* controller = CaptureModeController::Get();
  const base::FilePath default_folder =
      controller->delegate_for_testing()->GetUserDefaultDownloadsFolder();
  const base::FilePath custom_folder(FILE_PATH_LITERAL("/home/random"));
  controller->SetCustomCaptureFolder(custom_folder);
  StartImageRegionCapture();

  // Open the settings menu and check there already exists an item for that
  // pre-configured custom folder. Since the custom folder is unavailable, the
  // item should be disabled and dimmed. The item of the default folder should
  // be checked.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  WaitForSettingsMenuToBeRefreshed();

  CaptureModeSettingsTestApi test_api;
  EXPECT_TRUE(test_api.GetDefaultDownloadsOption());
  auto* custom_folder_view = test_api.GetCustomFolderOptionIfAny();
  EXPECT_TRUE(custom_folder_view);
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kCustomFolder));
  EXPECT_FALSE(custom_folder_view->GetEnabled());
  EXPECT_EQ(u"random",
            save_to_menu_group->GetOptionLabelForTesting(kCustomFolder));

  // Now open the folder selection dialog and select an available folder. The
  // item of the custom folder should be checked and enabled.
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());

  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  const base::FilePath new_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  dialog_factory->AcceptPath(new_folder);
  WaitForSettingsMenuToBeRefreshed();
  EXPECT_EQ(custom_folder_view, test_api.GetCustomFolderOptionIfAny());
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  EXPECT_TRUE(custom_folder_view->GetEnabled());
  EXPECT_EQ(u"test",
            save_to_menu_group->GetOptionLabelForTesting(kCustomFolder));
}

TEST_F(CaptureModeSettingsTest, DeleteCustomFolderFromDialog) {
  // Start a new session with a pre-configured custom folder.
  auto* controller = CaptureModeController::Get();
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  controller->SetCustomCaptureFolder(custom_folder);
  StartImageRegionCapture();

  // Open the settings menu and check there exists an item for that custom
  // folder. And the item is checked to indicate the current folder in use to
  // save the captured files is the custom folder.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  WaitForSettingsMenuToBeRefreshed();

  CaptureModeSettingsTestApi test_api;
  EXPECT_TRUE(test_api.GetDefaultDownloadsOption());
  auto* custom_folder_view = test_api.GetCustomFolderOptionIfAny();
  EXPECT_TRUE(custom_folder_view);
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));

  // Now open the folder selection dialog and delete the custom folder. Check
  // the item on the settings menu for custom folder is still there but disabled
  // and dimmed. The item of the default folder is checked now.
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    const bool result = base::DeleteFile(custom_folder);
    DCHECK(result);
  }
  dialog_factory->CancelDialog();
  WaitForSettingsMenuToBeRefreshed();
  EXPECT_TRUE(custom_folder_view);
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kCustomFolder));
  EXPECT_FALSE(custom_folder_view->GetEnabled());
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
}

TEST_F(CaptureModeSettingsTest, AccessibleCheckedStateChange) {
  // Start a new session with a pre-configured custom folder.
  ui::AXNodeData data;
  auto* controller = CaptureModeController::Get();
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  controller->SetCustomCaptureFolder(custom_folder);
  StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  WaitForSettingsMenuToBeRefreshed();

  CaptureModeSettingsTestApi test_api;
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();

  auto* checked_custom_folder_view =
      save_to_menu_group->SetOptionCheckedForTesting(kCustomFolder, true);
  checked_custom_folder_view->GetViewAccessibility().GetAccessibleNodeData(
      &data);
  EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kTrue);

  data = ui::AXNodeData();
  auto* unchecked_custom_folder_view =
      save_to_menu_group->SetOptionCheckedForTesting(kCustomFolder, false);
  unchecked_custom_folder_view->GetViewAccessibility().GetAccessibleNodeData(
      &data);
  EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kFalse);
}

TEST_F(CaptureModeSettingsTest, AcceptDefaultDownloadsFolderFromDialog) {
  // Start a new session with a pre-configured custom folder.
  auto* controller = CaptureModeController::Get();
  controller->SetCustomCaptureFolder(
      base::FilePath(FILE_PATH_LITERAL("/home/tests/foo")));
  StartImageRegionCapture();

  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  WaitForSettingsMenuToBeRefreshed();
  CaptureModeSettingsTestApi test_api;
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);

  // Selecting the same folder as the default downloads folder should result in
  // removing the custom folder option from the menu.
  auto* test_delegate = controller->delegate_for_testing();
  const auto default_downloads_folder =
      test_delegate->GetUserDefaultDownloadsFolder();
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  dialog_factory->AcceptPath(default_downloads_folder);
  EXPECT_FALSE(IsFolderSelectionDialogShown());
  EXPECT_TRUE(test_api.GetDefaultDownloadsOption());
  EXPECT_FALSE(test_api.GetCustomFolderOptionIfAny());
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
}

TEST_F(CaptureModeSettingsTest, SwitchWhichFolderToUserFromOptions) {
  // Start a new session with a pre-configured custom folder.
  auto* controller = CaptureModeController::Get();
  const base::FilePath custom_path(
      (CreateCustomFolderInUserDownloadsPath("test")));
  controller->SetCustomCaptureFolder(custom_path);
  StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  WaitForSettingsMenuToBeRefreshed();

  // Clicking the "Downloads" option will set it as the folder of choice, but
  // won't clear the custom folder.
  CaptureModeSettingsTestApi test_api;
  ClickOnView(test_api.GetDefaultDownloadsOption(), event_generator);
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kCustomFolder));
  const auto default_downloads_folder =
      controller->delegate_for_testing()->GetUserDefaultDownloadsFolder();
  auto capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, default_downloads_folder);
  EXPECT_TRUE(capture_folder.is_default_downloads_folder);
  EXPECT_EQ(custom_path, controller->GetCustomCaptureFolder());

  // Clicking on the custom folder option will switch back to using it.
  ClickOnView(test_api.GetCustomFolderOptionIfAny(), event_generator);
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  capture_folder = controller->GetCurrentCaptureFolder();
  EXPECT_EQ(capture_folder.path, custom_path);
  EXPECT_FALSE(capture_folder.is_default_downloads_folder);
}

// Tests that when there's no overlap betwwen capture label widget and settings
// widget, capture label widget is shown/hidden correctly after open/close the
// folder selection window.
TEST_F(CaptureModeSettingsTest, CaptureLabelViewNotOverlapsWithSettingsView) {
  // Update the display size to make sure capture label widget will not
  // overlap with settings widget
  UpdateDisplay("800x600");

  auto* controller = CaptureModeController::Get();
  // Set the region at an area far away from where the settings menu shows.
  controller->SetUserCaptureRegion(gfx::Rect(200, 200), /*by_user=*/true);

  StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();

  // Tests that the capture label widget doesn't overlap with settings widget.
  // Both capture label widget and settings widget are visible.
  views::Widget* capture_label_widget = GetCaptureModeLabelWidget();
  ClickOnView(GetSettingsButton(), event_generator);
  views::Widget* settings_widget = GetCaptureModeSettingsWidget();
  EXPECT_FALSE(capture_label_widget->GetWindowBoundsInScreen().Intersects(
      settings_widget->GetWindowBoundsInScreen()));
  EXPECT_TRUE(capture_label_widget->IsVisible());
  EXPECT_TRUE(settings_widget->IsVisible());

  // Open folder selection window, check that both capture label widget and
  // settings widget are invisible.
  CaptureModeSettingsTestApi test_api;
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
  EXPECT_FALSE(capture_label_widget->IsVisible());
  EXPECT_FALSE(settings_widget->IsVisible());

  // Now close folder selection window, check that capture label widget and
  // settings widget become visible.
  dialog_factory->CancelDialog();
  EXPECT_FALSE(IsFolderSelectionDialogShown());
  EXPECT_TRUE(capture_label_widget->IsVisible());
  EXPECT_EQ(capture_label_widget->GetLayer()->GetTargetOpacity(), 1.f);
  EXPECT_TRUE(settings_widget->IsVisible());

  // Close settings widget. Capture label widget is visible.
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_TRUE(capture_label_widget->IsVisible());
  controller->Stop();
}

// Tests that when capture label widget overlaps with settings widget, capture
// label widget is shown/hidden correctly after open/close the folder selection
// window, open/close settings menu. Regression test for
// https://crbug.com/1279606.
TEST_F(CaptureModeSettingsTest, CaptureLabelViewOverlapsWithSettingsView) {
  // Update display size to make capture label widget overlap with settings
  // widget.
  UpdateDisplay("1100x700");
  auto* controller = StartImageRegionCapture();
  auto* event_generator = GetEventGenerator();

  // Tests that capture label widget overlaps with settings widget and is
  // hidden after setting widget is shown.
  auto* capture_label_widget = GetCaptureModeLabelWidget();
  ClickOnView(GetSettingsButton(), event_generator);
  auto* settings_widget = GetCaptureModeSettingsWidget();
  EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects(
      settings_widget->GetWindowBoundsInScreen()));
  EXPECT_FALSE(GetCaptureModeLabelWidget()->IsVisible());
  EXPECT_TRUE(settings_widget->IsVisible());

  // Open folder selection window, capture label widget is invisible.
  CaptureModeSettingsTestApi test_api;
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  ClickOnView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
  EXPECT_FALSE(capture_label_widget->IsVisible());

  // Close folder selection window, capture label widget is invisible.
  dialog_factory->CancelDialog();
  EXPECT_FALSE(IsFolderSelectionDialogShown());
  EXPECT_FALSE(capture_label_widget->IsVisible());

  // Tests that capture label widget is visible after settings widget is
  // closed.
  ClickOnView(GetSettingsButton(), event_generator);
  EXPECT_TRUE(capture_label_widget->IsVisible());
  EXPECT_EQ(capture_label_widget->GetLayer()->GetTargetOpacity(), 1.f);
  controller->Stop();
}

TEST_F(CaptureModeSettingsTest, PressingEnterSelectsFocusedItem) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage);

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi session_test_api(
      controller->capture_mode_session());

  // Tab six times to focus on the settings button.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kSettingsClose,
            session_test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, session_test_api.GetCurrentFocusIndex());

  // Press the enter key to open the settings menu. The current focus group
  // should be `kPendingSettings`.
  SendKey(ui::VKEY_RETURN, event_generator);
  ASSERT_TRUE(GetCaptureModeSettingsView());
  EXPECT_EQ(FocusGroup::kPendingSettings,
            session_test_api.GetCurrentFocusGroup());

  // Tab once to enter focus into the settings menu.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  ASSERT_EQ(FocusGroup::kSettingsMenu, session_test_api.GetCurrentFocusGroup());

  // Tab until focus reaches the `kAudioMicrophone` option.
  CaptureModeSettingsTestApi settings_test_api;
  auto* mic_option = settings_test_api.GetMicrophoneOption();
  while (session_test_api.GetCurrentFocusedView()->GetView() != mic_option) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  CaptureModeMenuGroup* audio_input_menu_group =
      settings_test_api.GetAudioInputMenuGroup();
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioOff));
  EXPECT_FALSE(audio_input_menu_group->IsOptionChecked(kAudioMicrophone));

  // Press the enter key, and now microphone should be on.
  SendKey(ui::VKEY_RETURN, event_generator);
  EXPECT_FALSE(audio_input_menu_group->IsOptionChecked(kAudioOff));
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioMicrophone));
}

// Tests the basic keyboard navigation functions for the settings menu.
TEST_F(CaptureModeSettingsTest, KeyboardNavigationForSettingsMenu) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage);

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi session_test_api(
      controller->capture_mode_session());

  // Tab six times to focus on the settings button.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kSettingsClose,
            session_test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, session_test_api.GetCurrentFocusIndex());

  // Enter space to open the settings menu. The current focus group should be
  // `kPendingSettings`.
  SendKey(ui::VKEY_SPACE, event_generator);
  ASSERT_TRUE(GetCaptureModeSettingsView());
  EXPECT_EQ(FocusGroup::kPendingSettings,
            session_test_api.GetCurrentFocusGroup());

  CaptureModeSettingsTestApi settings_test_api;
  CaptureModeMenuGroup* audio_input_menu_group =
      settings_test_api.GetAudioInputMenuGroup();

  // Tab once to focus on the first item in the settings menu (`Audio input`
  // header).
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsMenu, session_test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, session_test_api.GetCurrentFocusIndex());

  // Tab once to enter focus into the settings menu.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  ASSERT_EQ(FocusGroup::kSettingsMenu, session_test_api.GetCurrentFocusGroup());

  // Check that the `Off` option is the checked option not the `Microphone`.
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioOff));
  EXPECT_FALSE(audio_input_menu_group->IsOptionChecked(kAudioMicrophone));

  // Tab until focus reaches the `kAudioMicrophone` option.
  auto* mic_option = settings_test_api.GetMicrophoneOption();
  while (session_test_api.GetCurrentFocusedView()->GetView() != mic_option) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // Enter space, and check that now the `Microphone` option is checked.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_FALSE(audio_input_menu_group->IsOptionChecked(kAudioOff));
  EXPECT_TRUE(audio_input_menu_group->IsOptionChecked(kAudioMicrophone));

  Switch* toggle_button =
      settings_test_api.GetDemoToolsMenuToggleButton()->toggle_button();

    // The demo tools toggle button will be disabled by default.
    EXPECT_FALSE(toggle_button->GetIsOn());

    // Tab until focus reaches the demo tools toggle button and enter space to
    // enable it.
    while (session_test_api.GetCurrentFocusedView()->GetView() !=
           toggle_button) {
      SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
    }
    SendKey(ui::VKEY_SPACE, event_generator);
    EXPECT_TRUE(toggle_button->GetIsOn());

  // Tab until focus reaches the `Select folder...` menu item.
  auto* select_folder_option = settings_test_api.GetSelectFolderMenuItem();
  while (session_test_api.GetCurrentFocusedView()->GetView() !=
         select_folder_option) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // Enter space to open the folder selection window.
  SendKey(ui::VKEY_SPACE, event_generator);
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  EXPECT_TRUE(IsFolderSelectionDialogShown());

  // Close selection window.
  dialog_factory->CancelDialog();
  EXPECT_FALSE(IsFolderSelectionDialogShown());

  // Now tab once to focus on the settings button and enter space on it to close
  // the settings menu.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose,
            session_test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, session_test_api.GetCurrentFocusIndex());
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_FALSE(GetCaptureModeSettingsView());
}

// Tests that the disabled option in the settings menu will be skipped while
// tabbing through.
TEST_F(CaptureModeSettingsTest,
       KeyboardNavigationForSettingsMenuWithDisabledOption) {
  // Start a new session with a pre-configured unavailable custom folder.
  auto* controller = CaptureModeController::Get();
  const base::FilePath custom_folder(FILE_PATH_LITERAL("/home/random"));
  controller->SetCustomCaptureFolder(custom_folder);
  StartImageRegionCapture();

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi session_test_api(
      controller->capture_mode_session());

  // Tab six times to focus the settings button and enter space to open the
  // settings menu.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, 6);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(FocusGroup::kPendingSettings,
            session_test_api.GetCurrentFocusGroup());
  CaptureModeSettingsView* settings_menu = GetCaptureModeSettingsView();
  ASSERT_TRUE(settings_menu);

  // Since the custom folder is unavailable, the `kCustomFolder` should be
  // disabled and won't be returned via
  // `CaptureModeSettingsViews::GetHighlightableItems`.
  CaptureModeSettingsTestApi settings_test_api;
  auto* custom_folder_view = settings_test_api.GetCustomFolderOptionIfAny();
  ASSERT_TRUE(custom_folder_view);
  EXPECT_FALSE(custom_folder_view->GetEnabled());

  std::vector<CaptureModeSessionFocusCycler::HighlightableView*>
      highlightable_items = settings_menu->GetHighlightableItems();
  EXPECT_FALSE(base::Contains(
      highlightable_items, custom_folder_view,
      &CaptureModeSessionFocusCycler::HighlightableView::GetView));

  // Tab once to enter focus into the settings menu.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  ASSERT_EQ(FocusGroup::kSettingsMenu, session_test_api.GetCurrentFocusGroup());

  // Tab until the focus is on the default `Downloads` option.
  auto* downloads_option = settings_test_api.GetDefaultDownloadsOption();
  ASSERT_TRUE(downloads_option);
  while (session_test_api.GetCurrentFocusedView()->GetView() !=
         downloads_option) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // Tab once to check the disabled `kCustomFolder` option is skipped and now
  // the `Select folder...` menu item gets focused.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(session_test_api.GetCurrentFocusedView()->GetView(),
            settings_test_api.GetSelectFolderMenuItem());
}

// Tests that selecting the default `Downloads` folder as the custom folder via
// keyboard navigation doesn't lead to a crash. Regression test for
// https://crbug.com/1269373.
TEST_F(CaptureModeSettingsTest,
       KeyboardNavigationForRemovingCustomFolderOption) {
  // Start a new session with a pre-configured custom folder.
  auto* controller = CaptureModeController::Get();
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  controller->SetCustomCaptureFolder(custom_folder);
  StartImageRegionCapture();

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  // Tab six times to focus the settings button, then enter space to open the
  // settings menu. Wait for the settings menu to be refreshed.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  SendKey(ui::VKEY_SPACE, event_generator);
  WaitForSettingsMenuToBeRefreshed();
  EXPECT_EQ(FocusGroup::kPendingSettings, test_api.GetCurrentFocusGroup());
  CaptureModeSettingsView* settings_menu = GetCaptureModeSettingsView();
  ASSERT_TRUE(settings_menu);

  // Tab once to enter focus into the settings menu.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  ASSERT_EQ(FocusGroup::kSettingsMenu, test_api.GetCurrentFocusGroup());

  // Tab until focus reaches the `Select folder...` menu item.
  CaptureModeSettingsTestApi settings_test_api;
  auto* select_folder_option = settings_test_api.GetSelectFolderMenuItem();
  while (test_api.GetCurrentFocusedView()->GetView() != select_folder_option) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // Enter space to open the folder selection window.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());

  // Select the default `Downloads` folder as the custom folder which will
  // have custom folder option get removed.
  auto* test_delegate = controller->delegate_for_testing();
  const auto default_downloads_folder =
      test_delegate->GetUserDefaultDownloadsFolder();
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  dialog_factory->AcceptPath(default_downloads_folder);

  // Press space to ensure the folder selection window can be opened after the
  // custom folder is removed from the settings menu.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
  dialog_factory->CancelDialog();

  // Tab once to make sure there's no crash and the focus gets moved to
  // settings button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
}

// Tests that first time selecting a custom folder via keyboard navigation.
// After the custom folder is selected, tabbing one more time will move focus
// from the settings menu to the settings button.
TEST_F(CaptureModeSettingsTest, KeyboardNavigationForAddingCustomFolderOption) {
  auto* controller = CaptureModeController::Get();
  StartImageRegionCapture();

  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi session_test_api(
      controller->capture_mode_session());

  // Tab six times to focus on the settings button, then enter space to open
  // the settings menu.
  auto* event_generator = GetEventGenerator();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, 6);
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(FocusGroup::kPendingSettings,
            session_test_api.GetCurrentFocusGroup());
  CaptureModeSettingsView* settings_menu = GetCaptureModeSettingsView();
  ASSERT_TRUE(settings_menu);

  // Tab once to enter focus into the settings menu.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  ASSERT_EQ(FocusGroup::kSettingsMenu, session_test_api.GetCurrentFocusGroup());

  // Tab until focus reaches the `Select folder...` menu item.
  CaptureModeSettingsTestApi settings_test_api;
  auto* select_folder_option = settings_test_api.GetSelectFolderMenuItem();
  while (session_test_api.GetCurrentFocusedView()->GetView() !=
         select_folder_option) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE);
  }

  // Enter space to open the folder selection window.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());

  // Select the custom folder and wait for the settings menu to be refreshed.
  // The custom folder option should be added to the settings menu and checked.
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  controller->SetCustomCaptureFolder(custom_folder);
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  dialog_factory->AcceptPath(custom_folder);
  WaitForSettingsMenuToBeRefreshed();
  EXPECT_TRUE(settings_test_api.GetCustomFolderOptionIfAny());

  // Press space to ensure the folder selection window can be opened after the
  // custom folder is added to the settings menu.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
  dialog_factory->CancelDialog();

  // Tab once to make sure the focus gets moved to settings button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose,
            session_test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, session_test_api.GetCurrentFocusIndex());
}

// Tests the folder selection settings when it's recommended by policy.
TEST_F(CaptureModeSettingsTest, FolderRecommendedByPolicy) {
  auto* controller = StartImageRegionCapture();

  // Set the pref to recommended values.
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  test_delegate->set_policy_capture_path(
      {custom_folder,
       CaptureModeDelegate::CapturePathEnforcement::kRecommended});

  // Open settings.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  std::unique_ptr<CaptureModeSettingsTestApi> test_api =
      std::make_unique<CaptureModeSettingsTestApi>();
  WaitForSettingsMenuToBeRefreshed();

  // Custom folder is set, but Downloads option and select folder is enabled.
  EXPECT_FALSE(controller->IsCustomFolderManagedByPolicy());
  CaptureModeMenuGroup* save_to_menu_group = test_api->GetSaveToMenuGroup();
  EXPECT_FALSE(save_to_menu_group->IsManagedByPolicy());

  EXPECT_TRUE(test_api->GetCustomFolderOptionIfAny()->GetEnabled());
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));

  EXPECT_TRUE(test_api->GetDefaultDownloadsOption()->GetEnabled());
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));

  EXPECT_TRUE(test_api->GetSelectFolderMenuItem()->GetEnabled());
}

// Tests the folder selection settings when it's enforced by policy.
TEST_F(CaptureModeSettingsTest, FolderSetByPolicy) {
  auto* controller = StartImageRegionCapture();

  // Set the pref to managed values.
  const base::FilePath custom_folder(
      CreateCustomFolderInUserDownloadsPath("test"));
  auto* test_delegate =
      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
  test_delegate->set_policy_capture_path(
      {custom_folder, CaptureModeDelegate::CapturePathEnforcement::kManaged});

  // Open settings.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetSettingsButton(), event_generator);
  std::unique_ptr<CaptureModeSettingsTestApi> test_api =
      std::make_unique<CaptureModeSettingsTestApi>();
  WaitForSettingsMenuToBeRefreshed();

  // Custom folder is set, but Downloads option and select folder are not
  // enabled.
  EXPECT_TRUE(controller->IsCustomFolderManagedByPolicy());
  CaptureModeMenuGroup* save_to_menu_group = test_api->GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsManagedByPolicy());

  EXPECT_TRUE(test_api->GetCustomFolderOptionIfAny()->GetEnabled());
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kCustomFolder));

  EXPECT_FALSE(test_api->GetDefaultDownloadsOption()->GetEnabled());
  EXPECT_FALSE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));

  EXPECT_FALSE(test_api->GetSelectFolderMenuItem()->GetEnabled());
}

// -----------------------------------------------------------------------------
// CaptureModeHistogramTest:

// Test fixture to verify screen capture histograms depending on the test
// param (true for tablet mode, false for clamshell mode).
class CaptureModeHistogramTest : public CaptureModeSettingsTest,
                                 public ::testing::WithParamInterface<bool> {
 public:
  CaptureModeHistogramTest() = default;
  ~CaptureModeHistogramTest() override = default;

  // CaptureModeSettingsTest:
  void SetUp() override {
    CaptureModeSettingsTest::SetUp();
    if (GetParam())
      SwitchToTabletMode();
  }

  void StartSessionForVideo() {
    StartCaptureSession(CaptureModeSource::kFullscreen,
                        CaptureModeType::kVideo);
  }

  void StartRecording() { CaptureModeTestApi().PerformCapture(); }

  void StopRecording() { CaptureModeTestApi().StopVideoRecording(); }

  void OpenView(const views::View* view,
                ui::test::EventGenerator* event_generator) {
    if (GetParam())
      TouchOnView(view, event_generator);
    else
      ClickOnView(view, event_generator);
  }
};

// Tests that metrics are recorded properly for various capture mode entry
// points.
TEST_P(CaptureModeHistogramTest, CaptureModeEntryPointHistograms) {
  constexpr char kHistogramNameBase[] = "EntryPoint";
  const std::string histogram_name = BuildHistogramName(
      kHistogramNameBase, /*behavior=*/nullptr, /*append_ui_mode_suffix=*/true);
  base::HistogramTester histogram_tester;

  auto* controller = CaptureModeController::Get();

  controller->Start(CaptureModeEntryType::kAccelTakeWindowScreenshot);
  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeEntryType::kAccelTakeWindowScreenshot, 1);
  controller->Stop();

  controller->Start(CaptureModeEntryType::kAccelTakePartialScreenshot);
  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeEntryType::kAccelTakePartialScreenshot, 1);
  controller->Stop();

  controller->Start(CaptureModeEntryType::kQuickSettings);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     CaptureModeEntryType::kQuickSettings, 1);
  controller->Stop();

  controller->Start(CaptureModeEntryType::kStylusPalette);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     CaptureModeEntryType::kStylusPalette, 1);
  controller->Stop();

  std::unique_ptr<aura::Window> window(
      CreateTestWindow(gfx::Rect(10, 20, 700, 500)));
  controller->CaptureScreenshotOfGivenWindow(window.get());
  WaitForCaptureFileToBeSaved();
  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeEntryType::kCaptureGivenWindow, 1);

  // Check total counts for each histogram to ensure calls aren't counted in
  // multiple buckets.
  histogram_tester.ExpectTotalCount(histogram_name, 5);
  histogram_tester.ExpectTotalCount(histogram_name, 5);
}

// Tests that metrics are recorded properly for capture mode configurations when
// taking a screenshot.
TEST_P(CaptureModeHistogramTest, ScreenshotConfigurationHistogram) {
  constexpr char kHistogramNameBase[] = "CaptureConfiguration";
  const std::string histogram_name = BuildHistogramName(
      kHistogramNameBase, /*behavior=*/nullptr, /*append_ui_mode_suffix=*/true);
  base::HistogramTester histogram_tester;
  // Use a set display size as we will be choosing points in this test.
  UpdateDisplay("800x700");

  // Create a window for window captures later.
  std::unique_ptr<aura::Window> window(
      CreateTestWindow(gfx::Rect(600, 600, 100, 100)));

  // Perform a fullscreen screenshot.
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kImage);
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeConfiguration::kFullscreenScreenshot, 1);

  // Perform a region screenshot.
  controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage);
  const gfx::Rect capture_region(200, 200, 400, 400);
  SelectRegion(capture_region);
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeConfiguration::kRegionScreenshot, 1);

  // Perform a window screenshot.
  controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window.get());
  EXPECT_EQ(window.get(),
            controller->capture_mode_session()->GetSelectedWindow());
  controller->PerformCapture();
  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeConfiguration::kWindowScreenshot, 1);
}

TEST_P(CaptureModeHistogramTest, VideoRecordingAudioVideoMetrics) {
  constexpr char kHistogramNameBase[] = "AudioRecordingMode";
  const std::string histogram_name = BuildHistogramName(
      kHistogramNameBase, /*behavior=*/nullptr, /*append_ui_mode_suffix=*/true);
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(histogram_name, AudioRecordingMode::kOff,
                                     0);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     AudioRecordingMode::kMicrophone, 0);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     AudioRecordingMode::kSystem, 0);
  histogram_tester.ExpectBucketCount(
      histogram_name, AudioRecordingMode::kSystemAndMicrophone, 0);

  // Perform a video recording with audio off. `kOff` should be recorded.
  StartSessionForVideo();
  CaptureModeTestApi().SetAudioRecordingMode(AudioRecordingMode::kOff);
  StartRecording();
  histogram_tester.ExpectBucketCount(histogram_name, AudioRecordingMode::kOff,
                                     1);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     AudioRecordingMode::kMicrophone, 0);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     AudioRecordingMode::kSystem, 0);
  histogram_tester.ExpectBucketCount(
      histogram_name, AudioRecordingMode::kSystemAndMicrophone, 0);
  WaitForSeconds(1);
  StopRecording();
  WaitForCaptureFileToBeSaved();

  histogram_tester.ExpectTotalCount(
      BuildHistogramName("ScreenRecordingFileSize", /*behavior=*/nullptr,
                         /*append_ui_mode_suffix=*/true),
      /*expected_count=*/1);

  // Perform a video recording with microphone audio recording on. `kMicrophone`
  // should be recorded.
  StartSessionForVideo();
  CaptureModeTestApi().SetAudioRecordingMode(AudioRecordingMode::kMicrophone);
  StartRecording();
  histogram_tester.ExpectBucketCount(histogram_name, AudioRecordingMode::kOff,
                                     1);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     AudioRecordingMode::kMicrophone, 1);
  histogram_tester.ExpectBucketCount(histogram_name,
                                     AudioRecordingMode::kSystem, 0);
  histogram_tester.ExpectBucketCount(
      histogram_name, AudioRecordingMode::kSystemAndMicrophone, 0);
  StopRecording();
}

TEST_P(CaptureModeHistogramTest, CaptureModeSwitchToDefaultReasonMetric) {
  constexpr char kHistogramNameBase[] = "SwitchToDefaultReason";
  const std::string histogram_name = BuildHistogramName(
      kHistogramNameBase, /*behavior=*/nullptr, /*append_ui_mode_suffix=*/true);
  base::HistogramTester histogram_tester;
  auto* controller = CaptureModeController::Get();
  const auto downloads_folder =
      controller->delegate_for_testing()->GetUserDefaultDownloadsFolder();
  const base::FilePath non_available_custom_folder(
      FILE_PATH_LITERAL("/home/test"));
  const base::FilePath available_custom_folder =
      CreateCustomFolderInUserDownloadsPath("test");

  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeSwitchToDefaultReason::kFolderUnavailable, 0);
  histogram_tester.ExpectBucketCount(
      histogram_name,
      CaptureModeSwitchToDefaultReason::kUserSelectedFromFolderSelectionDialog,
      0);
  histogram_tester.ExpectBucketCount(
      histogram_name,
      CaptureModeSwitchToDefaultReason::kUserSelectedFromSettingsMenu, 0);

  StartImageRegionCapture();

  // Set the custom folder to an unavailable folder the switch to default
  // reason should be recorded as `kFolderUnavailable`.
  controller->SetCustomCaptureFolder(non_available_custom_folder);
  EXPECT_EQ(controller->GetCurrentCaptureFolder().path,
            non_available_custom_folder);
  auto* event_generator = GetEventGenerator();
  OpenView(GetSettingsButton(), event_generator);
  WaitForSettingsMenuToBeRefreshed();
  CaptureModeSettingsTestApi test_api;
  CaptureModeMenuGroup* save_to_menu_group = test_api.GetSaveToMenuGroup();
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  histogram_tester.ExpectBucketCount(
      histogram_name, CaptureModeSwitchToDefaultReason::kFolderUnavailable, 1);

  // Select the save-to location to default downloads folder from folder
  // selection dialog and the switch to default reason should be recorded as
  // kUserSelectedFromSettingsMenu.
  controller->SetCustomCaptureFolder(available_custom_folder);
  EXPECT_EQ(controller->GetCurrentCaptureFolder().path,
            available_custom_folder);
  OpenView(test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(IsFolderSelectionDialogShown());
  auto* dialog_factory = FakeFolderSelectionDialogFactory::Get();
  dialog_factory->AcceptPath(downloads_folder);
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  histogram_tester.ExpectBucketCount(
      histogram_name,
      CaptureModeSwitchToDefaultReason::kUserSelectedFromFolderSelectionDialog,
      1);

  // Select the save-to location to default downloads folder from settings
  // menu and the switch to default reason should be recorded as
  // `kUserSelectedFromFolderSelectionDialog`.
  controller->SetCustomCaptureFolder(available_custom_folder);
  EXPECT_EQ(controller->GetCurrentCaptureFolder().path,
            available_custom_folder);
  OpenView(test_api.GetDefaultDownloadsOption(), event_generator);
  EXPECT_TRUE(save_to_menu_group->IsOptionChecked(kDownloadsFolder));
  histogram_tester.ExpectBucketCount(
      histogram_name,
      CaptureModeSwitchToDefaultReason::kUserSelectedFromSettingsMenu, 1);
}

INSTANTIATE_TEST_SUITE_P(All, CaptureModeHistogramTest, ::testing::Bool());

}  // namespace ash