chromium/ash/capture_mode/capture_mode_camera_unittests.cc

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

#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/autoclick/autoclick_controller.h"
#include "ash/capture_mode/capture_mode_bar_view.h"
#include "ash/capture_mode/capture_mode_camera_controller.h"
#include "ash/capture_mode/capture_mode_camera_preview_view.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_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_test_util.h"
#include "ash/capture_mode/capture_mode_toast_controller.h"
#include "ash/capture_mode/capture_mode_types.h"
#include "ash/capture_mode/capture_mode_util.h"
#include "ash/capture_mode/fake_camera_device.h"
#include "ash/capture_mode/fake_folder_selection_dialog_factory.h"
#include "ash/capture_mode/fake_video_source_provider.h"
#include "ash/capture_mode/test_capture_mode_delegate.h"
#include "ash/constants/ash_features.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/public/cpp/capture_mode/capture_mode_test_api.h"
#include "ash/public/cpp/holding_space/holding_space_controller.h"
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "ash/public/cpp/holding_space/holding_space_prefs.h"
#include "ash/public/cpp/holding_space/holding_space_test_api.h"
#include "ash/public/cpp/holding_space/holding_space_util.h"
#include "ash/public/cpp/holding_space/mock_holding_space_client.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/tab_slider_button.h"
#include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
#include "ash/system/notification_center/notification_center_test_api.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/privacy/privacy_indicators_controller.h"
#include "ash/system/privacy/privacy_indicators_tray_item_view.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_util.h"
#include "ash/wm/window_state.h"
#include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/system_monitor.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/timer/timer.h"
#include "cc/paint/skia_paint_canvas.h"
#include "chromeos/ui/frame/frame_header.h"
#include "components/viz/test/test_in_process_context_provider.h"
#include "media/base/video_facing.h"
#include "media/base/video_frame.h"
#include "media/renderers/paint_canvas_video_renderer.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_types.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/message_center/message_center.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_util.h"

namespace ash {

namespace {

constexpr char kTestUser[] = "user@test";

// The app ID used for the capture mode camera and microphone recording privacy
// indicators.
constexpr char kCaptureModePrivacyIndicatorId[] = "system-capture-mode";

// The minimum length (i.e. either width or height) of the user-selected region
// such that the camera preview can be visible, and intersecting with the
// capture button when it's positioned in the center of the region, regardless
// of whether the recording type drop down button is visible or not.
constexpr int kMinRegionLengthForCameraToIntersectLabelButton =
    capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera + 20;

CaptureModeCameraController* GetCameraController() {
  return CaptureModeController::Get()->camera_controller();
}

// Returns the current root window where the current capture activities are
// hosted in.
aura::Window* GetCurrentRoot() {
  auto* controller = CaptureModeController::Get();
  if (controller->IsActive())
    return controller->capture_mode_session()->current_root();

  if (controller->is_recording_in_progress()) {
    return controller->video_recording_watcher_for_testing()
        ->window_being_recorded()
        ->GetRootWindow();
  }

  return Shell::GetPrimaryRootWindow();
}

bool IsWindowStackedRightBelow(aura::Window* window, aura::Window* sibling) {
  DCHECK_EQ(window->parent(), sibling->parent());
  const auto& children = window->parent()->children();
  const int sibling_index =
      base::ranges::find(children, sibling) - children.begin();
  return sibling_index > 0 && children[sibling_index - 1] == window;
}

gfx::Rect GetTooSmallToFitCameraRegion() {
  return {100, 100,
          capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 1,
          capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 1};
}

}  // namespace

class CaptureModeCameraTest : public AshTestBase {
 public:
  CaptureModeCameraTest() = default;
  CaptureModeCameraTest(const CaptureModeCameraTest&) = delete;
  CaptureModeCameraTest& operator=(const CaptureModeCameraTest&) = delete;
  ~CaptureModeCameraTest() override = default;

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();
    FakeFolderSelectionDialogFactory::Start();
    window_ = CreateTestWindow(gfx::Rect(30, 40, 600, 500));
  }

  void TearDown() override {
    window_.reset();
    FakeFolderSelectionDialogFactory::Stop();
    AshTestBase::TearDown();
  }

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

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

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

  // Adds the default camera, sets it as the selected camera, then removes it,
  // which triggers the camera disconnection grace period. Returns a pointer to
  // the `CaptureModeCameraController`.
  CaptureModeCameraController* AddAndRemoveCameraAndTriggerGracePeriod() {
    AddDefaultCamera();
    auto* camera_controller = GetCameraController();
    camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
    RemoveDefaultCamera();
    return camera_controller;
  }

  void OpenSettingsView() {
    BaseCaptureModeSession* session =
        CaptureModeController::Get()->capture_mode_session();
    DCHECK(session);
    ClickOnView(CaptureModeSessionTestApi(session)
                    .GetCaptureModeBarView()
                    ->settings_button(),
                GetEventGenerator());
  }

  void DragPreviewToPoint(views::Widget* preview_widget,
                          const gfx::Point& screen_location,
                          bool by_touch_gestures = false,
                          bool drop = true) {
    DCHECK(preview_widget);
    auto* event_generator = GetEventGenerator();
    event_generator->set_current_screen_location(
        preview_widget->GetWindowBoundsInScreen().CenterPoint());
    if (by_touch_gestures) {
      event_generator->PressTouch();
      // Move the touch by an enough amount in X to make sure it generates a
      // serial of gesture scroll events instead of a fling event.
      event_generator->MoveTouchBy(50, 0);
      event_generator->MoveTouch(screen_location);
      if (drop)
        event_generator->ReleaseTouch();
    } else {
      event_generator->PressLeftButton();
      event_generator->MoveMouseTo(screen_location);
      if (drop)
        event_generator->ReleaseLeftButton();
    }
  }

  CameraPreviewResizeButton* GetPreviewResizeButton() const {
    return GetCameraController()->camera_preview_view()->resize_button();
  }

  // Verifies that the camera preview is placed on the correct position based on
  // current preview snap position and the given `confine_bounds_in_screen`.
  void VerifyPreviewAlignment(const gfx::Rect& confine_bounds_in_screen) {
    auto* camera_controller = GetCameraController();
    const auto* preview_widget = camera_controller->camera_preview_widget();
    DCHECK(preview_widget);
    const gfx::Rect camera_preview_bounds =
        preview_widget->GetWindowBoundsInScreen();

    switch (camera_controller->camera_preview_snap_position()) {
      case CameraPreviewSnapPosition::kTopLeft: {
        gfx::Point expect_origin = confine_bounds_in_screen.origin();
        expect_origin.Offset(capture_mode::kSpaceBetweenCameraPreviewAndEdges,
                             capture_mode::kSpaceBetweenCameraPreviewAndEdges);
        EXPECT_EQ(expect_origin, camera_preview_bounds.origin());
        break;
      }
      case CameraPreviewSnapPosition::kBottomLeft: {
        const gfx::Point expect_bottom_left =
            gfx::Point(confine_bounds_in_screen.x() +
                           capture_mode::kSpaceBetweenCameraPreviewAndEdges,
                       confine_bounds_in_screen.bottom() -
                           capture_mode::kSpaceBetweenCameraPreviewAndEdges);
        EXPECT_EQ(expect_bottom_left, camera_preview_bounds.bottom_left());
        break;
      }
      case CameraPreviewSnapPosition::kBottomRight: {
        const gfx::Point expect_bottom_right =
            gfx::Point(confine_bounds_in_screen.right() -
                           capture_mode::kSpaceBetweenCameraPreviewAndEdges,
                       confine_bounds_in_screen.bottom() -
                           capture_mode::kSpaceBetweenCameraPreviewAndEdges);
        EXPECT_EQ(expect_bottom_right, camera_preview_bounds.bottom_right());
        break;
      }
      case CameraPreviewSnapPosition::kTopRight: {
        const gfx::Point expect_top_right =
            gfx::Point(confine_bounds_in_screen.right() -
                           capture_mode::kSpaceBetweenCameraPreviewAndEdges,
                       confine_bounds_in_screen.y() +
                           capture_mode::kSpaceBetweenCameraPreviewAndEdges);
        EXPECT_EQ(expect_top_right, camera_preview_bounds.top_right());
        break;
      }
    }
  }

  // Verifies that the icon image and the tooltip of the resize button gets
  // updated correctly when pressed.
  void VerifyResizeButton(bool is_collapsed,
                          CameraPreviewResizeButton* resize_button) {
    SkColor color =
        resize_button->GetColorProvider()->GetColor(kColorAshIconColorPrimary);
    const gfx::ImageSkia collapse_icon_image =
        gfx::CreateVectorIcon(kCaptureModeCameraPreviewCollapseIcon, color);
    const gfx::ImageSkia expand_icon_image =
        gfx::CreateVectorIcon(kCaptureModeCameraPreviewExpandIcon, color);

    const SkBitmap* expected_icon = is_collapsed ? expand_icon_image.bitmap()
                                                 : collapse_icon_image.bitmap();
    const SkBitmap* actual_icon =
        resize_button->GetImage(views::ImageButton::ButtonState::STATE_NORMAL)
            .bitmap();
    EXPECT_TRUE(gfx::test::AreBitmapsEqual(*actual_icon, *expected_icon));

    const auto expected_tooltip_text = l10n_util::GetStringUTF16(
        is_collapsed ? IDS_ASH_SCREEN_CAPTURE_TOOLTIP_EXPAND_SELFIE_CAMERA
                     : IDS_ASH_SCREEN_CAPTURE_TOOLTIP_COLLAPSE_SELFIE_CAMERA);
    EXPECT_EQ(resize_button->GetTooltipText(), expected_tooltip_text);
  }

  // Select capture region by pressing and dragging the mouse.
  void SelectCaptureRegion(const gfx::Rect& region, bool release_mouse = true) {
    auto* controller = CaptureModeController::Get();
    ASSERT_TRUE(controller->IsActive());
    ASSERT_EQ(CaptureModeSource::kRegion, controller->source());
    auto* event_generator = GetEventGenerator();
    event_generator->set_current_screen_location(region.origin());
    event_generator->PressLeftButton();
    event_generator->MoveMouseTo(region.bottom_right());
    if (release_mouse)
      event_generator->ReleaseLeftButton();
    EXPECT_EQ(region, controller->user_capture_region());
  }

  void ConvertToPipWindow(aura::Window* pip_window) {
    WindowState* window_state = WindowState::Get(pip_window);
    DCHECK(!window_state->IsPip());
    views::Widget::GetWidgetForNativeWindow(pip_window)
        ->SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
    pip_window->SetProperty(kWindowPipTypeKey, true);
    DCHECK(window_state->IsPip());
  }

  bool IsCameraIndicatorIconVisible() const {
    auto* indicator_view = GetPrimaryDisplayPrivacyIndicatorsView();
    return indicator_view && indicator_view->GetVisible() &&
           PrivacyIndicatorsController::Get()->IsCameraUsed() &&
           indicator_view->camera_icon()->GetVisible();
  }

  bool IsMicrophoneIndicatorIconVisible() const {
    auto* indicator_view = GetPrimaryDisplayPrivacyIndicatorsView();
    return indicator_view && indicator_view->GetVisible() &&
           PrivacyIndicatorsController::Get()->IsMicrophoneUsed() &&
           indicator_view->microphone_icon()->GetVisible();
  }

  PrivacyIndicatorsTrayItemView* GetPrimaryDisplayPrivacyIndicatorsView()
      const {
    return Shell::GetPrimaryRootWindowController()
        ->GetStatusAreaWidget()
        ->notification_center_tray()
        ->privacy_indicators_view();
  }

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

TEST_F(CaptureModeCameraTest, SizeSpecsBigEnoughRegion) {
  gfx::Size confine_bounds_size(800, 700);
  {
    auto specs = capture_mode_util::CalculateCameraPreviewSizeSpecs(
        confine_bounds_size,
        /*is_collapsed=*/false);
    EXPECT_TRUE(specs.is_collapsible);
    EXPECT_TRUE(specs.should_be_visible);
    EXPECT_EQ(specs.size.width(), specs.size.height());
    EXPECT_EQ(specs.size.width(),
              700 / capture_mode::kCaptureSurfaceShortSideDivider);
  }

  // Transposing the confine bounds (e.g. due to rotation) should have no effect
  // since we always consider the shorter side.
  confine_bounds_size.Transpose();
  {
    auto specs = capture_mode_util::CalculateCameraPreviewSizeSpecs(
        confine_bounds_size,
        /*is_collapsed=*/false);
    EXPECT_TRUE(specs.is_collapsible);
    EXPECT_TRUE(specs.should_be_visible);
    EXPECT_EQ(specs.size.width(),
              700 / capture_mode::kCaptureSurfaceShortSideDivider);
  }

  {
    auto specs = capture_mode_util::CalculateCameraPreviewSizeSpecs(
        confine_bounds_size,
        /*is_collapsed=*/true);
    EXPECT_TRUE(specs.is_collapsible);
    EXPECT_TRUE(specs.should_be_visible);
    EXPECT_EQ(specs.size.width(), capture_mode::kMinCameraPreviewDiameter);
  }
}

TEST_F(CaptureModeCameraTest, SizeSpecsNotCollapsible) {
  gfx::Size confine_bounds_size(800, 500);
  auto specs = capture_mode_util::CalculateCameraPreviewSizeSpecs(
      confine_bounds_size,
      /*is_collapsed=*/false);
  EXPECT_FALSE(specs.is_collapsible);
  EXPECT_TRUE(specs.should_be_visible);
  EXPECT_EQ(specs.size.width(), specs.size.height());
  EXPECT_EQ(specs.size.width(),
            500 / capture_mode::kCaptureSurfaceShortSideDivider);
  EXPECT_FALSE(specs.is_surface_too_small);
}

TEST_F(CaptureModeCameraTest, SizeSpecsHiddenPreview) {
  gfx::Size confine_bounds_size(800, 170);
  auto specs = capture_mode_util::CalculateCameraPreviewSizeSpecs(
      confine_bounds_size,
      /*is_collapsed=*/false);
  EXPECT_FALSE(specs.is_collapsible);
  EXPECT_FALSE(specs.should_be_visible);
  EXPECT_EQ(specs.size.width(), specs.size.height());
  EXPECT_EQ(specs.size.width(), capture_mode::kMinCameraPreviewDiameter);
  EXPECT_TRUE(specs.is_surface_too_small);
}

TEST_F(CaptureModeCameraTest, CameraDevicesChanges) {
  auto* camera_controller = GetCameraController();
  ASSERT_TRUE(camera_controller);
  EXPECT_TRUE(camera_controller->available_cameras().empty());
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  EXPECT_FALSE(camera_controller->should_show_preview());
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  const std::string device_id = "/dev/video0";
  const std::string display_name = "Integrated Webcam";
  const std::string model_id = "0123:4567";
  AddFakeCamera(device_id, display_name, model_id);

  EXPECT_EQ(1u, camera_controller->available_cameras().size());
  EXPECT_TRUE(camera_controller->available_cameras()[0].camera_id.is_valid());
  EXPECT_EQ(model_id, camera_controller->available_cameras()[0]
                          .camera_id.model_id_or_display_name());
  EXPECT_EQ(1, camera_controller->available_cameras()[0].camera_id.number());
  EXPECT_EQ(device_id, camera_controller->available_cameras()[0].device_id);
  EXPECT_EQ(display_name,
            camera_controller->available_cameras()[0].display_name);
  EXPECT_FALSE(camera_controller->should_show_preview());
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  RemoveFakeCamera(device_id);

  EXPECT_TRUE(camera_controller->available_cameras().empty());
  EXPECT_FALSE(camera_controller->should_show_preview());
  EXPECT_FALSE(camera_controller->camera_preview_widget());
}

TEST_F(CaptureModeCameraTest, CameraRemovedWhileWaitingForCameraDevices) {
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  EXPECT_EQ(1u, camera_controller->available_cameras().size());

  // The system monitor can trigger several notifications about devices changes
  // for the same camera addition event. We will simulate a camera getting
  // removed right while we're still waiting for the video source provider to
  // send us the list. https://crbug.com/1295377.
  auto* video_source_provider = GetTestDelegate()->video_source_provider();
  {
    base::RunLoop loop;
    video_source_provider->set_on_replied_with_source_infos(
        base::BindLambdaForTesting([&]() { loop.Quit(); }));
    base::SystemMonitor::Get()->ProcessDevicesChanged(
        base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
    loop.Run();
  }

  RemoveDefaultCamera();
  EXPECT_TRUE(camera_controller->available_cameras().empty());
}

TEST_F(CaptureModeCameraTest, SelectingUnavailableCamera) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // Selecting a camera that doesn't exist in the list shouldn't show its
  // preview.
  camera_controller->SetSelectedCamera(CameraId("model", 1));
  EXPECT_FALSE(camera_controller->camera_preview_widget());
}

TEST_F(CaptureModeCameraTest, SelectingAvailableCamera) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  AddDefaultCamera();

  EXPECT_EQ(1u, camera_controller->available_cameras().size());
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // Selecting an available camera while showing the preview is allowed should
  // result in creating the preview widget.
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  EXPECT_TRUE(camera_controller->camera_preview_widget());
}

TEST_F(CaptureModeCameraTest, SelectedCameraBecomesAvailable) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // The selected camera becomes available while `should_show_preview_` is still
  // true. The preview should show in this case.
  AddDefaultCamera();
  EXPECT_TRUE(camera_controller->camera_preview_widget());

  // Clearing the selected camera should hide the preview.
  camera_controller->SetSelectedCamera(CameraId());
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  EXPECT_FALSE(camera_controller->camera_preview_widget());
}

TEST_F(CaptureModeCameraTest, SelectingDifferentCameraCreatesNewPreviewWidget) {
  AddDefaultCamera();
  const std::string device_id = "/dev/video0";
  const std::string display_name = "Integrated Webcam";
  const std::string model_id = "0123:4567";
  AddFakeCamera(device_id, display_name, model_id);

  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* current_preview_widget = camera_controller->camera_preview_widget();
  EXPECT_TRUE(current_preview_widget);

  // Selecting a different camera should result in the recreation of the preview
  // widget.
  camera_controller->SetSelectedCamera(CameraId(model_id, 1));
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  EXPECT_NE(current_preview_widget, camera_controller->camera_preview_widget());
}

TEST_F(CaptureModeCameraTest, MultipleCamerasOfTheSameModel) {
  auto* camera_controller = GetCameraController();

  const std::string device_id_1 = "/dev/video0";
  const std::string display_name = "Integrated Webcam";
  const std::string model_id = "0123:4567";
  AddFakeCamera(device_id_1, display_name, model_id);

  const auto& available_cameras = camera_controller->available_cameras();
  EXPECT_EQ(1u, available_cameras.size());
  EXPECT_EQ(1, available_cameras[0].camera_id.number());
  EXPECT_EQ(model_id,
            available_cameras[0].camera_id.model_id_or_display_name());

  // Adding a new camera of the same model should be correctly tracked with a
  // different ID.
  const std::string device_id_2 = "/dev/video1";
  AddFakeCamera(device_id_2, display_name, model_id);

  EXPECT_EQ(2u, available_cameras.size());
  EXPECT_EQ(2, available_cameras[1].camera_id.number());
  EXPECT_EQ(model_id,
            available_cameras[1].camera_id.model_id_or_display_name());
  EXPECT_NE(available_cameras[0].camera_id, available_cameras[1].camera_id);
}

TEST_F(CaptureModeCameraTest, MissingCameraModelId) {
  auto* camera_controller = GetCameraController();

  const std::string device_id = "/dev/video0";
  const std::string display_name = "Integrated Webcam";
  AddFakeCamera(device_id, display_name, /*model_id=*/"");

  // The camera's display name should be used instead of a model ID when it's
  // missing.
  const auto& available_cameras = camera_controller->available_cameras();
  EXPECT_EQ(1u, available_cameras.size());
  EXPECT_TRUE(available_cameras[0].camera_id.is_valid());
  EXPECT_EQ(1, available_cameras[0].camera_id.number());
  EXPECT_EQ(display_name,
            available_cameras[0].camera_id.model_id_or_display_name());

  // If the SystemMonitor triggered a device change alert for some reason, but
  // the actual list of cameras didn't change, observers should never be
  // notified again.
  {
    base::RunLoop loop;
    camera_controller->SetOnCameraListReceivedForTesting(loop.QuitClosure());
    CameraDevicesChangeWaiter observer;
    base::SystemMonitor::Get()->ProcessDevicesChanged(
        base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
    loop.Run();
    EXPECT_EQ(0, observer.camera_change_event_count());
  }
}

TEST_F(CaptureModeCameraTest, CameraFramesFlipping) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  int index = 0;
  for (const auto facing_mode :
       {media::MEDIA_VIDEO_FACING_NONE, media::MEDIA_VIDEO_FACING_USER,
        media::MEDIA_VIDEO_FACING_ENVIRONMENT}) {
    const std::string device_id = base::StringPrintf("/dev/video%d", index);
    const std::string display_name = base::StringPrintf("Camera %d", index);
    AddFakeCamera(device_id, display_name, display_name, facing_mode);
    camera_controller->SetSelectedCamera(CameraId(display_name, 1));
    EXPECT_TRUE(camera_controller->camera_preview_widget());
    const bool should_be_flipped =
        facing_mode != media::MEDIA_VIDEO_FACING_ENVIRONMENT;
    EXPECT_EQ(should_be_flipped, camera_controller->camera_preview_view()
                                     ->should_flip_frames_horizontally())
        << "Failed for facing mode: " << facing_mode;
    ++index;
  }
}

TEST_F(CaptureModeCameraTest, DisconnectSelectedCamera) {
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  const CameraId camera_id(kDefaultCameraModelId, 1);
  camera_controller->SetSelectedCamera(camera_id);

  // Disconnect a selected camera, and expect that the grace period timer is
  // running.
  RemoveDefaultCamera();
  base::OneShotTimer* timer =
      camera_controller->camera_reconnect_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());
  EXPECT_EQ(camera_id, camera_controller->selected_camera());

  // When the timer fires before the camera gets reconnected, the selected
  // camera ID is cleared.
  timer->FireNow();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  EXPECT_FALSE(timer->IsRunning());
}

TEST_F(CaptureModeCameraTest, SelectUnavailableCameraDuringGracePeriod) {
  auto* camera_controller = AddAndRemoveCameraAndTriggerGracePeriod();
  base::OneShotTimer* timer =
      camera_controller->camera_reconnect_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());

  // Selecting an unavailable camera during the grace period should keep the
  // timer running for another grace period.
  const CameraId new_camera_id("Different Camera", 1);
  camera_controller->SetSelectedCamera(new_camera_id);
  EXPECT_TRUE(timer->IsRunning());
  EXPECT_EQ(new_camera_id, camera_controller->selected_camera());

  // Once the timer fires the new ID will also be cleared.
  timer->FireNow();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  EXPECT_FALSE(timer->IsRunning());
}

TEST_F(CaptureModeCameraTest, SelectAvailableCameraDuringGracePeriod) {
  const std::string device_id = "/dev/video0";
  const std::string display_name = "Integrated Webcam";
  const CameraId available_camera_id(display_name, 1);
  AddFakeCamera(device_id, display_name, /*model_id=*/"");

  // This adds the default camera as the selected one, and removes it triggering
  // a grace period.
  auto* camera_controller = AddAndRemoveCameraAndTriggerGracePeriod();
  base::OneShotTimer* timer =
      camera_controller->camera_reconnect_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());

  // Selecting the available camera during the grace period should stop the
  // timer immediately.
  camera_controller->SetSelectedCamera(available_camera_id);
  EXPECT_FALSE(timer->IsRunning());
  EXPECT_EQ(available_camera_id, camera_controller->selected_camera());
}

// This tests simulates a flaky camera connection.
TEST_F(CaptureModeCameraTest, ReconnectDuringGracePeriod) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = AddAndRemoveCameraAndTriggerGracePeriod();
  base::OneShotTimer* timer =
      camera_controller->camera_reconnect_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // Re-add the camera during the grace period, the timer should stop, and the
  // preview should reshow.
  AddDefaultCamera();
  EXPECT_FALSE(timer->IsRunning());
  EXPECT_TRUE(camera_controller->camera_preview_widget());
}

// Tests a flaky camera that disconnects before recording begins and reconnects
// during recording within the grace period.
TEST_F(CaptureModeCameraTest, ReconnectDuringGracePeriodAfterRecordingStarts) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = AddAndRemoveCameraAndTriggerGracePeriod();
  base::OneShotTimer* timer =
      camera_controller->camera_reconnect_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // Start recording, and expect that `should_show_preview_` will change to
  // false.
  EXPECT_TRUE(camera_controller->should_show_preview());
  StartVideoRecordingImmediately();
  EXPECT_FALSE(camera_controller->should_show_preview());

  // Re-add the camera during the grace period, the timer should stop, but the
  // preview should not be recreated.
  AddDefaultCamera();
  EXPECT_FALSE(timer->IsRunning());
  EXPECT_FALSE(camera_controller->camera_preview_widget());
}

TEST_F(CaptureModeCameraTest, SelectedCameraChangedObserver) {
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  const CameraId camera_id(kDefaultCameraModelId, 1);

  CameraDevicesChangeWaiter observer;
  camera_controller->SetSelectedCamera(camera_id);
  EXPECT_EQ(1, observer.selected_camera_change_event_count());

  // Selecting the same camera ID again should not trigger an observer call.
  camera_controller->SetSelectedCamera(camera_id);
  EXPECT_EQ(1, observer.selected_camera_change_event_count());

  // Clearing the ID should.
  camera_controller->SetSelectedCamera(CameraId());
  EXPECT_EQ(2, observer.selected_camera_change_event_count());
}

TEST_F(CaptureModeCameraTest, ShouldShowPreviewTest) {
  auto* controller = CaptureModeController::Get();
  auto* camera_controller = GetCameraController();
  controller->SetSource(CaptureModeSource::kFullscreen);
  controller->SetType(CaptureModeType::kVideo);
  controller->Start(CaptureModeEntryType::kQuickSettings);
  // should_show_preview() should return true when CaptureModeSession is started
  // in video recording mode.
  EXPECT_TRUE(camera_controller->should_show_preview());
  // Switch to image capture mode, should_show_preview() should return false.
  controller->SetType(CaptureModeType::kImage);
  EXPECT_FALSE(camera_controller->should_show_preview());
  // Stop an existing capture session, should_show_preview() should return
  // false.
  controller->Stop();
  EXPECT_FALSE(camera_controller->should_show_preview());
  EXPECT_FALSE(controller->IsActive());

  // Start another capture session and start video recording,
  // should_show_preview() should return false when video recording ends.
  controller->SetType(CaptureModeType::kVideo);
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_TRUE(camera_controller->should_show_preview());
  controller->StartVideoRecordingImmediatelyForTesting();
  EXPECT_TRUE(camera_controller->should_show_preview());
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  EXPECT_FALSE(camera_controller->should_show_preview());
}

TEST_F(CaptureModeCameraTest, ManagedByPolicyCameraOptions) {
  GetTestDelegate()->set_is_camera_disabled_by_policy(true);

  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  OpenSettingsView();

  // At this moment, there are no camera devices connected. The camera menu
  // group should be hidden.
  CaptureModeSettingsTestApi test_api;
  CaptureModeMenuGroup* camera_menu_group = test_api.GetCameraMenuGroup();
  ASSERT_TRUE(camera_menu_group);
  EXPECT_FALSE(camera_menu_group->GetVisible());

  // Camera addition/removal are still observed even when managed by policy, but
  // once a camera is added, the group becomes visible, but shows only a dimmed
  // "Off" option.
  AddDefaultCamera();
  EXPECT_TRUE(camera_menu_group->GetVisible());
  EXPECT_TRUE(camera_menu_group->IsOptionChecked(kCameraOff));
  EXPECT_FALSE(camera_menu_group->IsOptionEnabled(kCameraOff));
  EXPECT_FALSE(test_api.GetCameraOption(kCameraDevicesBegin));

  // Selecting a camera will be ignored.
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  EXPECT_TRUE(camera_menu_group->IsOptionChecked(kCameraOff));
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // Removing the existing camera should hide the camera menu group and remove
  // all its options.
  RemoveDefaultCamera();
  EXPECT_FALSE(camera_menu_group->GetVisible());
  EXPECT_FALSE(test_api.GetCameraOption(kCameraOff));
}

// Tests that the options on camera menu are shown and checked correctly when
// adding or removing cameras. Also tests that `selected_camera_` is updated
// correspondently.
TEST_F(CaptureModeCameraTest, CheckCameraOptions) {
  auto* camera_controller = GetCameraController();
  const std::string device_id_1 = "/dev/video0";
  const std::string display_name_1 = "Integrated Webcam";

  const std::string device_id_2 = "/dev/video1";
  const std::string display_name_2 = "Integrated Webcam 1";

  AddFakeCamera(device_id_1, display_name_1, display_name_1);
  AddFakeCamera(device_id_2, display_name_2, display_name_2);

  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  OpenSettingsView();

  // Check camera settings is shown. `Off` option is checked. Two camera options
  // are not checked.
  CaptureModeSettingsTestApi test_api;
  CaptureModeMenuGroup* camera_menu_group = test_api.GetCameraMenuGroup();
  EXPECT_TRUE(camera_menu_group && camera_menu_group->GetVisible());
  EXPECT_TRUE(camera_menu_group->IsOptionChecked(kCameraOff));
  EXPECT_FALSE(camera_menu_group->IsOptionChecked(kCameraDevicesBegin));
  EXPECT_FALSE(camera_menu_group->IsOptionChecked(kCameraDevicesBegin + 1));
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());

  // Click the option for camera device 1, check its display name matches with
  // the camera device 1 display name and it's checked. Also check the selected
  // camera is valid now.
  ClickOnView(test_api.GetCameraOption(kCameraDevicesBegin),
              GetEventGenerator());
  EXPECT_FALSE(camera_menu_group->IsOptionChecked(kCameraOff));
  EXPECT_EQ(base::UTF16ToUTF8(camera_menu_group->GetOptionLabelForTesting(
                kCameraDevicesBegin)),
            display_name_1);
  EXPECT_TRUE(camera_menu_group->IsOptionChecked(kCameraDevicesBegin));
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());

  // Now disconnect camera device 1.
  RemoveFakeCamera(device_id_1);

  // Check the camera device 1 is removed from the camera menu. There's only a
  // camera option for camera device 2. Selected camera is still valid. `Off`
  // option and option for camera device 2 are not checked.
  EXPECT_TRUE(test_api.GetCameraOption(kCameraDevicesBegin));
  EXPECT_FALSE(test_api.GetCameraOption(kCameraDevicesBegin + 1));
  EXPECT_EQ(base::UTF16ToUTF8(camera_menu_group->GetOptionLabelForTesting(
                kCameraDevicesBegin)),
            display_name_2);
  EXPECT_FALSE(camera_menu_group->IsOptionChecked(kCameraOff));
  EXPECT_FALSE(camera_menu_group->IsOptionChecked(kCameraDevicesBegin));
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());

  // Now connect the camera device 1 again.
  AddFakeCamera(device_id_1, display_name_1, display_name_1);

  // Check the camera device 1 is added back to the camera menu and it's checked
  // automatically. Check the selected option's label matches with the camera
  // device 1's display name.
  // Note that `device_id_1` ("/dev/video0") comes before `device_id_2`
  // ("/dev/video1") in sort order, so it will be added first in the menu.
  EXPECT_TRUE(test_api.GetCameraOption(kCameraDevicesBegin));
  EXPECT_TRUE(test_api.GetCameraOption(kCameraDevicesBegin + 1));
  EXPECT_FALSE(camera_menu_group->IsOptionChecked(kCameraOff));
  EXPECT_TRUE(camera_menu_group->IsOptionChecked(kCameraDevicesBegin));
  EXPECT_FALSE(camera_menu_group->IsOptionChecked(kCameraDevicesBegin + 1));
  EXPECT_EQ(base::UTF16ToUTF8(camera_menu_group->GetOptionLabelForTesting(
                kCameraDevicesBegin)),
            display_name_1);
  EXPECT_EQ(base::UTF16ToUTF8(camera_menu_group->GetOptionLabelForTesting(
                kCameraDevicesBegin + 1)),
            display_name_2);
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
}

TEST_F(CaptureModeCameraTest, CameraPreviewWidgetStackingInFullscreen) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  base::RunLoop().RunUntilIdle();
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  EXPECT_TRUE(camera_preview_widget);

  auto* preview_window = camera_preview_widget->GetNativeWindow();
  const auto* menu_container = preview_window->GetRootWindow()->GetChildById(
      kShellWindowId_MenuContainer);
  auto* parent = preview_window->parent();
  // Parent of the preview should be the MenuContainer when capture mode
  // session is active with `kFullscreen` type. And the preview window should
  // be the bottom-most child of it.
  EXPECT_EQ(parent, menu_container);
  EXPECT_EQ(menu_container->children().front(), preview_window);

  StartRecordingFromSource(CaptureModeSource::kFullscreen);
  // Parent of the preview should be the MenuContainer when video recording
  // in progress with `kFullscreen` type. And the preview window should be the
  // top-most child of it.
  preview_window = camera_preview_widget->GetNativeWindow();
  parent = preview_window->parent();
  EXPECT_EQ(parent, menu_container);
  EXPECT_EQ(menu_container->children().back(), preview_window);
}

TEST_F(CaptureModeCameraTest, CameraPreviewWidgetStackingInRegion) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  EXPECT_TRUE(camera_preview_widget);

  auto* preview_window = camera_preview_widget->GetNativeWindow();
  // Parent of the preview should be the UnparentedContainer when the user
  // capture region is not set.
  EXPECT_TRUE(controller->user_capture_region().IsEmpty());
  EXPECT_EQ(preview_window->parent(),
            preview_window->GetRootWindow()->GetChildById(
                kShellWindowId_UnparentedContainer));
  EXPECT_FALSE(camera_preview_widget->IsVisible());

  controller->SetUserCaptureRegion(gfx::Rect(10, 20, 80, 60),
                                   /*by_user=*/true);
  StartRecordingFromSource(CaptureModeSource::kRegion);
  const auto* menu_container = preview_window->GetRootWindow()->GetChildById(
      kShellWindowId_MenuContainer);
  // Parent of the preview should be the MenuContainer when video recording
  // in progress with `kRegion` type. And the preview window should be the
  // top-most child of it.
  ASSERT_EQ(preview_window->parent(), menu_container);
  EXPECT_EQ(menu_container->children().back(), preview_window);
}

// Tests that camera preview widget is shown, hidden and parented correctly
// while moving, dragging and updating the user selection region.
TEST_F(CaptureModeCameraTest, CameraPreviewWhileUpdatingCaptureRegion) {
  UpdateDisplay("800x700");
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  auto* capture_session = controller->capture_mode_session();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  EXPECT_TRUE(camera_preview_widget);
  auto* preview_window = camera_preview_widget->GetNativeWindow();

  const int min_region_length =
      capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera;
  const gfx::Rect capture_region(10, 20, min_region_length, min_region_length);
  controller->SetUserCaptureRegion(capture_region, /*by_user=*/true);

  // After user capture region is set, parent of the preview should be the
  // MenuContainer.
  const auto* menu_container = preview_window->GetRootWindow()->GetChildById(
      kShellWindowId_MenuContainer);
  ASSERT_EQ(preview_window->parent(), menu_container);
  EXPECT_TRUE(camera_preview_widget->IsVisible());
  EXPECT_TRUE(preview_window->IsVisible());

  // Press the bottom right of selection region. Verify preview is hidden and
  // it's still parented to `menu_container`.
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(capture_region.bottom_right());
  event_generator->PressLeftButton();
  EXPECT_FALSE(camera_preview_widget->IsVisible());
  EXPECT_FALSE(preview_window->IsVisible());
  ASSERT_EQ(preview_window->parent(), menu_container);

  // Move mouse to update the selection region. Verify preview is still
  // hidden.
  const gfx::Vector2d delta(15, 20);
  event_generator->MoveMouseTo(capture_region.bottom_right() + delta);
  EXPECT_TRUE(capture_session->is_drag_in_progress());
  EXPECT_FALSE(camera_preview_widget->IsVisible());
  EXPECT_FALSE(preview_window->IsVisible());
  ASSERT_EQ(preview_window->parent(), menu_container);

  // Now release the drag to end selection region update. Verify preview is
  // shown and parent of the preview should be MenuContainer.
  event_generator->ReleaseLeftButton();
  EXPECT_FALSE(capture_session->is_drag_in_progress());
  EXPECT_TRUE(camera_preview_widget->IsVisible());
  EXPECT_TRUE(preview_window->IsVisible());
  EXPECT_EQ(preview_window->parent(), menu_container);

  // Press in the selection region to move it around. Since in the
  // use case, selection region is not updated, preview should not be hidden.
  const gfx::Point current_position(capture_region.origin() + delta);
  event_generator->set_current_screen_location(current_position);
  event_generator->PressLeftButton();
  EXPECT_TRUE(camera_preview_widget->IsVisible());
  EXPECT_EQ(preview_window->parent(), menu_container);

  // Move mouse to move selection region around. Verify preview is shown.
  event_generator->MoveMouseTo(current_position + delta);
  EXPECT_TRUE(camera_preview_widget->IsVisible());
  EXPECT_EQ(preview_window->parent(), menu_container);

  // Now release the move to end moving selection region. Verify preview is
  // shown.
  event_generator->ReleaseLeftButton();
  EXPECT_TRUE(camera_preview_widget->IsVisible());
  EXPECT_EQ(preview_window->parent(), menu_container);
}

TEST_F(CaptureModeCameraTest, CameraPreviewWidgetStackingInWindow) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  EXPECT_TRUE(camera_preview_widget);

  auto* preview_window = camera_preview_widget->GetNativeWindow();
  // The parent of the preview should be the UnparentedContainer when selected
  // window is not set.
  ASSERT_FALSE(controller->capture_mode_session()->GetSelectedWindow());
  EXPECT_EQ(preview_window->parent(),
            preview_window->GetRootWindow()->GetChildById(
                kShellWindowId_UnparentedContainer));
  EXPECT_FALSE(camera_preview_widget->IsVisible());

  StartRecordingFromSource(CaptureModeSource::kWindow);
  // Parent of the preview widget should be the window being recorded when video
  // recording in progress with `kWindow` type. And the preview window should be
  // the top-most child of it.
  const auto* parent = preview_window->parent();
  const auto* window_being_recorded =
      controller->video_recording_watcher_for_testing()
          ->window_being_recorded();
  ASSERT_EQ(parent, window_being_recorded);
  EXPECT_EQ(window_being_recorded->children().back(), preview_window);
}

// Tests the visibility of camera menu when there's no camera connected.
TEST_F(CaptureModeCameraTest, CheckCameraMenuVisibility) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  OpenSettingsView();

  // Check camera menu is hidden.
  CaptureModeSettingsTestApi test_api;
  CaptureModeMenuGroup* camera_menu_group = test_api.GetCameraMenuGroup();
  EXPECT_TRUE(camera_menu_group && !camera_menu_group->GetVisible());

  // Connect a camera.
  AddDefaultCamera();

  // Check the camera menu is shown. And there's an `Off` option and an option
  // for the connected camera.
  camera_menu_group = test_api.GetCameraMenuGroup();
  EXPECT_TRUE(camera_menu_group && camera_menu_group->GetVisible());
  EXPECT_TRUE(test_api.GetCameraOption(kCameraOff));
  EXPECT_TRUE(test_api.GetCameraOption(kCameraDevicesBegin));

  // Now disconnect the camera.
  RemoveDefaultCamera();

  // Check the menu group is hidden again and all options has been removed from
  // the menu.
  camera_menu_group = test_api.GetCameraMenuGroup();
  EXPECT_TRUE(camera_menu_group && !camera_menu_group->GetVisible());
  EXPECT_FALSE(test_api.GetCameraOption(kCameraOff));
  EXPECT_FALSE(test_api.GetCameraOption(kCameraDevicesBegin));
}

TEST_F(CaptureModeCameraTest, CameraPreviewWidgetBounds) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  ASSERT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());

  const auto* preview_widget = camera_controller->camera_preview_widget();
  EXPECT_TRUE(preview_widget);

  // Verifies the camera preview's alignment with `kBottomRight` snap position
  // and `kFullscreen` capture source.
  const auto* capture_mode_session = controller->capture_mode_session();
  const gfx::Rect work_area =
      display::Screen::GetScreen()
          ->GetDisplayNearestWindow(capture_mode_session->current_root())
          .work_area();
  VerifyPreviewAlignment(work_area);

  // Switching to `kRegion` without capture region set, the preview widget
  // should not be shown.
  controller->SetSource(CaptureModeSource::kRegion);
  EXPECT_TRUE(controller->user_capture_region().IsEmpty());
  EXPECT_FALSE(preview_widget->IsVisible());

  // Verifies the camera preview's alignment with `kBottomRight` snap position
  // and `kRegion` capture source.
  const gfx::Rect capture_region(10, 20, 300, 200);
  controller->SetUserCaptureRegion(capture_region, /*by_user=*/true);
  VerifyPreviewAlignment(capture_region);

  // Verifies the camera preview's alignment after switching back to
  // `kFullscreen.`
  controller->SetSource(CaptureModeSource::kFullscreen);
  VerifyPreviewAlignment(work_area);

  // Verifies the camera preview's alignment with `kBottomLeft` snap position
  // and `kRegion` capture source.
  controller->SetSource(CaptureModeSource::kRegion);
  camera_controller->SetCameraPreviewSnapPosition(
      CameraPreviewSnapPosition::kBottomLeft);
  VerifyPreviewAlignment(capture_region);

  // Verifies the camera preview's alignment with `kTopLeft` snap position
  // and `kRegion` capture source.
  camera_controller->SetCameraPreviewSnapPosition(
      CameraPreviewSnapPosition::kTopLeft);
  VerifyPreviewAlignment(capture_region);

  // Verifies the camera preview's alignment with `kTopRight` snap position
  // and `kRegion` capture source.
  camera_controller->SetCameraPreviewSnapPosition(
      CameraPreviewSnapPosition::kTopRight);
  VerifyPreviewAlignment(capture_region);

  // Set capture region to empty, the preview should be hidden again.
  controller->SetUserCaptureRegion(gfx::Rect(), /*by_user=*/true);
  EXPECT_FALSE(preview_widget->IsVisible());

  // Verifies the camera preview's alignment with `kTopRight` snap position and
  // `kWindow` capture source.
  StartRecordingFromSource(CaptureModeSource::kWindow);
  auto* window_being_recorded =
      controller->video_recording_watcher_for_testing()
          ->window_being_recorded();
  DCHECK(window_being_recorded);
  auto window_confine_bounds =
      capture_mode_util::GetCaptureWindowConfineBounds(window_being_recorded);
  wm::ConvertRectToScreen(window_being_recorded, &window_confine_bounds);
  VerifyPreviewAlignment(window_confine_bounds);
}

TEST_F(CaptureModeCameraTest, MultiDisplayCameraPreviewWidgetBounds) {
  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);

  // Start the capture session in the second display.
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  const gfx::Rect second_display_bounds(801, 0, 800, 700);
  // The camera preview should reside inside the second display when we start
  // capture session in the second display.
  const auto* preview_widget = camera_controller->camera_preview_widget();
  EXPECT_TRUE(second_display_bounds.Contains(
      preview_widget->GetWindowBoundsInScreen()));

  // Move the capture session to the primary display should move the camera
  // preview to the primary display as well.
  event_generator->MoveMouseTo(gfx::Point(10, 20));
  EXPECT_TRUE(gfx::Rect(0, 0, 800, 700)
                  .Contains(preview_widget->GetWindowBoundsInScreen()));

  // Move back to the second display, switch to `kRegion` and set the capture
  // region. The camera preview should be moved back to the second display and
  // inside the capture region.
  event_generator->MoveMouseTo(point_in_second_display);
  controller->SetSource(CaptureModeSource::kRegion);
  // The capture region set through `controller` is in root coordinate.
  const gfx::Rect capture_region(100, 0, 400, 550);
  controller->SetUserCaptureRegion(capture_region, /*by_user=*/true);
  const gfx::Rect capture_region_in_screen(901, 0, 400, 550);
  const gfx::Rect preview_bounds = preview_widget->GetWindowBoundsInScreen();
  EXPECT_TRUE(second_display_bounds.Contains(preview_bounds));
  EXPECT_TRUE(capture_region_in_screen.Contains(preview_bounds));

  // Start the window recording inside the second display, the camera preview
  // should be inside the window that is being recorded inside the second
  // display.
  window()->SetBoundsInScreen(
      gfx::Rect(900, 0, 600, 500),
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          Shell::GetAllRootWindows()[1]));
  StartRecordingFromSource(CaptureModeSource::kWindow);
  const auto* window_being_recorded =
      controller->video_recording_watcher_for_testing()
          ->window_being_recorded();
  EXPECT_TRUE(window_being_recorded->GetBoundsInScreen().Contains(
      preview_widget->GetWindowBoundsInScreen()));
}

// Tests that switching from `kImage` to `kVideo` with capture source `kWindow`,
// and capture window is already selected before the switch, the camera preview
// widget should be positioned and parented correctly.
TEST_F(CaptureModeCameraTest, CameraPreviewWidgetAfterTypeSwitched) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
  auto* camera_controller = GetCameraController();
  GetEventGenerator()->MoveMouseToCenterOf(window());

  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  controller->SetType(CaptureModeType::kVideo);
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  EXPECT_TRUE(camera_preview_widget);
  auto* camera_preview_window = camera_preview_widget->GetNativeWindow();
  const auto* selected_window =
      controller->capture_mode_session()->GetSelectedWindow();
  ASSERT_EQ(camera_preview_window->parent(), selected_window);

  // Verify that camera preview is at the bottom right corner of the window.
  VerifyPreviewAlignment(selected_window->GetBoundsInScreen());
  // `camera_preview_window` should not have a transient parent.
  EXPECT_FALSE(wm::GetTransientParent(camera_preview_window));
}

// Tests that audio and camera menu groups should be hidden from the settings
// menu when there's a video recording in progress.
TEST_F(CaptureModeCameraTest,
       AudioAndCameraMenuGroupsAreHiddenWhenVideoRecordingInProgress) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  auto* camera_controller = controller->camera_controller();
  StartVideoRecordingImmediately();
  EXPECT_TRUE(controller->is_recording_in_progress());

  // Verify there's no camera preview created, since we don't select any camera.
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // Check capture session is shut down after the video recording starts.
  EXPECT_FALSE(controller->IsActive());

  // Start a new session, check the type should be switched automatically to
  // kImage.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_EQ(CaptureModeType::kImage, controller->type());

  // Verify there's no camera preview created after a new session started.
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  OpenSettingsView();
  // Check the audio and camera menu groups are hidden from the settings.
  CaptureModeSettingsTestApi test_api_new;
  EXPECT_FALSE(test_api_new.GetCameraMenuGroup());
  EXPECT_FALSE(test_api_new.GetAudioInputMenuGroup());
  EXPECT_TRUE(test_api_new.GetSaveToMenuGroup());
}

// Verify that starting a new capture session and updating capture source won't
// affect the current camera preview when there's a video recording is progress.
TEST_F(CaptureModeCameraTest,
       CameraPreviewNotChangeWhenVideoRecordingInProgress) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  auto* camera_controller = controller->camera_controller();
  AddDefaultCamera();
  OpenSettingsView();

  // Select the default camera for video recording.
  CaptureModeSettingsTestApi test_api;
  ClickOnView(test_api.GetCameraOption(kCameraDevicesBegin),
              GetEventGenerator());
  StartVideoRecordingImmediately();

  // Check that the camera preview is created.
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  EXPECT_TRUE(camera_preview_widget);

  auto* preview_window = camera_preview_widget->GetNativeWindow();
  auto* parent = preview_window->parent();

  // Start a new capture session, and set capture source to `kFullscreen`,
  // verify the camera preview and its parent are not changed.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  controller->SetSource(CaptureModeSource::kFullscreen);
  EXPECT_EQ(preview_window,
            camera_controller->camera_preview_widget()->GetNativeWindow());
  EXPECT_EQ(
      parent,
      camera_controller->camera_preview_widget()->GetNativeWindow()->parent());

  // Update capture source to `kRegion` and set the user capture region, verify
  // the camera preview and its parent are not changed.
  controller->SetSource(CaptureModeSource::kRegion);
  controller->SetUserCaptureRegion({100, 100, 200, 300}, /*by_user=*/true);
  EXPECT_EQ(preview_window,
            camera_controller->camera_preview_widget()->GetNativeWindow());
  EXPECT_EQ(
      parent,
      camera_controller->camera_preview_widget()->GetNativeWindow()->parent());

  // Update capture source to `kWindow` and move mouse on top the `window`,
  // verify that the camera preview and its parent are not changed.
  controller->SetSource(CaptureModeSource::kWindow);
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window());
  EXPECT_EQ(preview_window,
            camera_controller->camera_preview_widget()->GetNativeWindow());
  EXPECT_EQ(
      parent,
      camera_controller->camera_preview_widget()->GetNativeWindow()->parent());
}

// Tests that changing the folder while there's a video recording in progress
// doesn't change the folder where the video being recorded will be saved to.
// It will only affect the image to be captured.
TEST_F(CaptureModeCameraTest, ChangeFolderWhileVideoRecordingInProgress) {
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  StartVideoRecordingImmediately();

  // While the video recording is in progress, start a new capture session and
  // update the save-to folder to the custom folder.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  controller->SetCustomCaptureFolder(
      CreateCustomFolderInUserDownloadsPath("test"));

  // Perform the image capture. Verify that the image is saved to the custom
  // folder.
  controller->PerformCapture();
  const base::FilePath& saved_image_file = WaitForCaptureFileToBeSaved();
  EXPECT_EQ(controller->GetCustomCaptureFolder(), saved_image_file.DirName());

  // End the video recoring and verify the video is still saved to the default
  // downloads folder.
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  const base::FilePath& saved_video_file = WaitForCaptureFileToBeSaved();
  EXPECT_EQ(controller->delegate_for_testing()->GetUserDefaultDownloadsFolder(),
            saved_video_file.DirName());
}

// Tests multiple scenarios to trigger selected window updates at located
// position. Camera preview's native window should be added to the ignore
// windows and no crash should happen in these cases.
TEST_F(CaptureModeCameraTest,
       UpdateSelectedWindowAtPositionWithCameraPreviewIgnored) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseToCenterOf(window());
  EXPECT_TRUE(camera_controller->camera_preview_widget());

  // No camera preview when it is in `kImage`.
  controller->SetType(CaptureModeType::kImage);
  event_generator->MoveMouseToCenterOf(window());
  EXPECT_FALSE(camera_controller->camera_preview_widget());

  // The native window of camera preview widget should be ignored from the
  // candidates of the selected window. So moving the mouse to be on top of the
  // camera preview should not cause any crash or selected window changes.
  controller->SetType(CaptureModeType::kVideo);
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  const auto* capture_mode_session = controller->capture_mode_session();
  event_generator->MoveMouseToCenterOf(
      camera_preview_widget->GetNativeWindow());
  EXPECT_EQ(window(), capture_mode_session->GetSelectedWindow());
  EXPECT_TRUE(window()->IsVisible());
  EXPECT_TRUE(camera_preview_widget->IsVisible());

  // Hide `window_` with camera preview on should not cause any crash and
  // selected window should be updated to nullptr.
  window()->Hide();
  EXPECT_FALSE(window()->IsVisible());
  EXPECT_FALSE(camera_preview_widget->IsVisible());
  EXPECT_FALSE(capture_mode_session->GetSelectedWindow());

  // Reshow `window_` without hovering over it should not set the selected
  // window. Camera preview should still be hidden as its parent hasn't been set
  // to `window_` yet.
  const auto* preview_native_window = camera_preview_widget->GetNativeWindow();
  window()->Show();
  EXPECT_TRUE(window()->IsVisible());
  EXPECT_FALSE(camera_preview_widget->IsVisible());
  EXPECT_FALSE(capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(preview_native_window->parent(),
            preview_native_window->GetRootWindow()->GetChildById(
                kShellWindowId_UnparentedContainer));

  // Hovering over `window_` should set it to the selected window, camera
  // preview widget should be reparented to it as well. And the camera preview
  // widget should be visible now.
  event_generator->MoveMouseToCenterOf(window());
  EXPECT_EQ(preview_native_window->parent(),
            capture_mode_session->GetSelectedWindow());
  EXPECT_TRUE(camera_preview_widget->IsVisible());
  EXPECT_EQ(window(), capture_mode_session->GetSelectedWindow());
}

// Tests that capture label's opacity changes accordingly when it's overlapped
// or it's not overlapped with camera preview. Also tests that when located
// events is or is not on capture label, its opacity is updated accordingly.
TEST_F(CaptureModeCameraTest,
       CaptureLabelOpacityChangeWhenOverlappingWithCameraPreview) {
  UpdateDisplay("900x800");
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  const auto* capture_label_widget = capture_session->capture_label_widget();
  const ui::Layer* capture_label_layer = capture_label_widget->GetLayer();

  // Set capture region big enough to make capture label not overlapping with
  // camera preview. Verify capture label is fully opaque.
  const gfx::Rect capture_region(100, 100, 700, 700);
  SelectCaptureRegion(capture_region);
  EXPECT_FALSE(capture_label_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.f);

  // Update capture region smaller to make capture label overlap with camera
  // preview. Verify capture label is `kOverlapOpacity`.
  // Make sure to resize the region to a value that won't cause the camera to be
  // hidden according to the camera size specs.
  const int delta_x =
      kMinRegionLengthForCameraToIntersectLabelButton - capture_region.width();
  const int delta_y =
      kMinRegionLengthForCameraToIntersectLabelButton - capture_region.height();
  const gfx::Vector2d delta(delta_x, delta_y);
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(capture_region.bottom_right());
  event_generator->PressLeftButton();
  event_generator->MoveMouseTo(capture_region.bottom_right() + delta);
  event_generator->ReleaseLeftButton();
  EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Move mouse on top of capture label, verify it's updated to fully opaque
  // even it's still overlapped with camera preview.
  const gfx::Rect capture_lable_bounds =
      capture_label_widget->GetWindowBoundsInScreen();
  event_generator->MoveMouseTo(capture_lable_bounds.CenterPoint());
  EXPECT_TRUE(capture_lable_bounds.Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.0f);

  // Move mouse to the outside of capture label, verify it's updated to
  // `kOverlapOpacity`.
  const gfx::Vector2d delta1(50, 50);
  event_generator->MoveMouseTo(capture_lable_bounds.bottom_right() + delta1);
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Click on the outside of the capture region to reset it, verify capture
  // label is updated to fully opaque.
  const gfx::Rect current_capture_region = controller->user_capture_region();
  event_generator->MoveMouseTo(current_capture_region.bottom_right() + delta1);
  event_generator->ClickLeftButton();
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.0f);
}

TEST_F(CaptureModeCameraTest,
       CaptureBarOpacityChangeWhenOverlappingWithCameraPreview) {
  // Update display size and update window with customized size to make sure
  // camera preview overlap with capture bar with capture source `kWindow`.
  UpdateDisplay("1366x768");
  window()->SetBounds({0, 195, 903, 492});

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  auto* capture_session = controller->capture_mode_session();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  const auto* capture_bar_widget = capture_session->GetCaptureModeBarWidget();
  const ui::Layer* capture_bar_layer = capture_bar_widget->GetLayer();

  // Move mouse on top of `window` to set the selected window. Verify capture
  // bar is `kOverlapOpacity`.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(window()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(capture_session->GetSelectedWindow(), window());
  EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Move mouse on top of capture bar. Verify capture bar is updated to fully
  // opaque.
  event_generator->MoveMouseTo(
      capture_bar_widget->GetWindowBoundsInScreen().CenterPoint());
  EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f);

  // Mouse mouse to the outside of capture bar, verify it's updated to
  // `kOverlapOpacity`.
  const gfx::Point capture_bar_origin =
      capture_bar_widget->GetWindowBoundsInScreen().origin();
  event_generator->MoveMouseTo(capture_bar_origin.x() - 10,
                               capture_bar_origin.y() - 10);
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);
}

TEST_F(CaptureModeCameraTest, CaptureBarOpacityChangeOnDisplayRotation) {
  // Update display size and update window with customized size to make sure
  // camera preview overlap with capture bar with capture source `kWindow`.
  UpdateDisplay("1366x768");
  window()->SetBounds({0, 195, 903, 492});

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  auto* capture_session = controller->capture_mode_session();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  const auto* capture_bar_widget = capture_session->GetCaptureModeBarWidget();
  const ui::Layer* capture_bar_layer = capture_bar_widget->GetLayer();

  // Move mouse on top of `window` to set the selected window. Verify capture
  // bar is `kOverlapOpacity`.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(window()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(capture_session->GetSelectedWindow(), window());
  EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Rotate the primary display by 90 degrees. Verify that capture bar no longer
  // overlaps with camera preview and it's updated to fully opaque.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(), display::Display::ROTATE_90,
      display::Display::RotationSource::USER);
  EXPECT_FALSE(capture_bar_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f);

  // Rotate the primary display by 180 degrees. Verify that capture bar is
  // overlapped with camera preview and it's updated to `kOverlapOpacity`.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(),
      display::Display::ROTATE_180, display::Display::RotationSource::USER);
  EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);
}

TEST_F(CaptureModeCameraTest, CaptureLabelOpacityChangeOnCaptureSourceChange) {
  UpdateDisplay("800x600");
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* camera_preview_widget = camera_controller->camera_preview_widget();
  auto* capture_label_widget = capture_session->capture_label_widget();
  ui::Layer* capture_label_layer = capture_label_widget->GetLayer();

  // Select capture region to make sure capture label is overlapped with
  // camera preview. Verify capture label is `kOverlapOpacity`.
  const int min_region_length = kMinRegionLengthForCameraToIntersectLabelButton;
  SelectCaptureRegion({100, 100, min_region_length, min_region_length});
  EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Change the capture source from `kRegion` to `kFullscreen`, verify capture
  // label is updated to fully opaque.
  controller->SetSource(CaptureModeSource::kFullscreen);
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.0f);
}

TEST_F(CaptureModeCameraTest,
       CaptureLabelOpacityChangeWhileVideoRecordingInProgress) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* camera_preview_widget = camera_controller->camera_preview_widget();
  const int min_region_length = kMinRegionLengthForCameraToIntersectLabelButton;
  controller->SetUserCaptureRegion(
      {100, 100, min_region_length, min_region_length}, /*by_user=*/true);

  StartVideoRecordingImmediately();
  EXPECT_FALSE(controller->IsActive());

  // Start a new capture session, verify even capture label is overlapped with
  // camera preview, it's still fully opaque since camera preview does not
  // belong to the new capture session.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  EXPECT_EQ(CaptureModeSource::kRegion, controller->source());
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);

  const auto* capture_label_widget = capture_session->capture_label_widget();
  EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_label_widget->GetLayer()->GetTargetOpacity(), 1.0f);
}

TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInFullscreen) {
  UpdateDisplay("800x700");
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  auto* event_generator = GetEventGenerator();
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  // Tests that the camera preview is focusable in fullscreen capture.
  auto* camera_preview_view = camera_controller->camera_preview_view();
  auto* resize_button = GetPreviewResizeButton();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());

  // Press tab again should advance the focus on the resize button. And the
  // resize button should be invisible before and visible after being focused.
  EXPECT_FALSE(resize_button->GetVisible());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_TRUE(resize_button->GetVisible());

  // Press space when the resize button is focused should collapse the camera
  // preview.
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_FALSE(camera_controller->is_camera_preview_collapsed());
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_TRUE(camera_controller->is_camera_preview_collapsed());
  // Press space again should expand the camera preview.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_FALSE(camera_controller->is_camera_preview_collapsed());

  // When the resize button inside the camera preview is focused, press tab
  // should advance to focus on the settings button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // The resize button should fade out and become invisible in
  // `kResizeButtonShowDuration` after removing focus from it.
  base::OneShotTimer* hide_timer =
      camera_preview_view->resize_button_hide_timer_for_test();
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_TRUE(hide_timer->IsRunning());
  EXPECT_EQ(hide_timer->GetCurrentDelay(),
            capture_mode::kResizeButtonShowDuration);
  {
    ViewVisibilityChangeWaiter waiter(resize_button);
    EXPECT_TRUE(resize_button->GetVisible());
    hide_timer->FireNow();
    waiter.Wait();
    EXPECT_FALSE(resize_button->GetVisible());
  }

  // Shift tab should advance the focus from the settings button back to the
  // resize button inside the camera preview.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());

  // The resize button should keep visible when it is focused, even trigger
  // `resize_button_hide_timer_` to refresh its visibility.
  hide_timer = camera_preview_view->resize_button_hide_timer_for_test();
  EXPECT_TRUE(hide_timer->IsRunning());
  EXPECT_TRUE(resize_button->GetVisible());
  hide_timer->FireNow();
  EXPECT_TRUE(resize_button->GetVisible());

  // Continue shift tab should move the focus from the resize button to the
  // camera preview.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());

  // The resize button should fade out and become invisible again in
  // `kResizeButtonShowDuration` after removing focus from it.
  hide_timer = camera_preview_view->resize_button_hide_timer_for_test();
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_TRUE(hide_timer->IsRunning());
  EXPECT_EQ(hide_timer->GetCurrentDelay(),
            capture_mode::kResizeButtonShowDuration);
  {
    ViewVisibilityChangeWaiter waiter(resize_button);
    EXPECT_TRUE(resize_button->GetVisible());
    hide_timer->FireNow();
    waiter.Wait();
    EXPECT_FALSE(resize_button->GetVisible());
  }

  // Tests moving the camera preview through the keyboard when it is focused.
  EXPECT_TRUE(camera_controller->camera_preview_view()->has_focus());
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());
  // Press control+right should not move the camera preview, as it is currently
  // at the right.
  SendKey(ui::VKEY_RIGHT, event_generator, ui::EF_CONTROL_DOWN);
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());
  // Press control+down should not move the camera preview either, as it is
  // currently at the bottom.
  SendKey(ui::VKEY_DOWN, event_generator, ui::EF_CONTROL_DOWN);
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());
  // Press control+left should move the camera preview from bottom right to
  // bottom left.
  SendKey(ui::VKEY_LEFT, event_generator, ui::EF_CONTROL_DOWN);
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomLeft,
            camera_controller->camera_preview_snap_position());
  // Press control+right now should work to move the camera preview from bottom
  // left to bottom right.
  SendKey(ui::VKEY_RIGHT, event_generator, ui::EF_CONTROL_DOWN);
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());
  // Press control+up should move the camera preview from bottom right to top
  // right.
  SendKey(ui::VKEY_UP, event_generator, ui::EF_CONTROL_DOWN);
  EXPECT_EQ(CameraPreviewSnapPosition::kTopRight,
            camera_controller->camera_preview_snap_position());

  // Shift tab again should advance the focus from the camera preview back to
  // the window capture source button.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());
}

TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInRegion) {
  UpdateDisplay("1366x768");
  auto* controller = CaptureModeController::Get();
  controller->SetUserCaptureRegion(gfx::Rect(10, 10, 800, 700),
                                   /*by_user=*/true);

  StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  auto* event_generator = GetEventGenerator();
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());

  // Tests that the camera preview is focusable in region capture.
  auto* camera_preview_view = camera_controller->camera_preview_view();
  auto* resize_button = GetPreviewResizeButton();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/15);
  EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());

  // When the resize button inside camera preview is focused, press tab should
  // advance to focus on the capture button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureButton, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Press tab until focus is on the settings button.
  while (FocusGroup::kSettingsClose != test_api.GetCurrentFocusGroup()) {
    SendKey(ui::VKEY_TAB, event_generator);
  }
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());

  // Shift tab should move the focus from the settings button back to the
  // capture button.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureButton, test_api.GetCurrentFocusGroup());
  // The index of the focused item depends on whether the recording type drop
  // down button exists or not.
  const size_t expected_index = features::IsGifRecordingEnabled() ? 1u : 0u;
  EXPECT_EQ(expected_index, test_api.GetCurrentFocusIndex());

  // Shift tab again until the focus is moved from the capture button back to
  // the resize button inside the camera preview.
  while (FocusGroup::kCameraPreview != test_api.GetCurrentFocusGroup()) {
    SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  }
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());
  // Continue shift tab should move the focus from the resize button to the
  // camera preview.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());

  // Shift tab to advance the focus back to the window capture source button.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN, /*count=*/10);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());

  // Update the capture region to test when the resize button is not focusable.
  controller->SetUserCaptureRegion(gfx::Rect(10, 10, 400, 550),
                                   /*by_user=*/true);
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/10);
  EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());
  // Press tab should advance the focus on the capture button instead of the
  // resize button. As the resize button is forced to be hidden, which is not
  // focusable in this case.
  EXPECT_FALSE(camera_preview_view->is_collapsible());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureButton, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
}

TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInWindow) {
  UpdateDisplay("1366x768");
  // Create one more window besides `window_`.
  std::unique_ptr<aura::Window> window2(
      CreateTestWindow(gfx::Rect(150, 50, 800, 700)));
  window()->SetBounds(gfx::Rect(30, 40, 800, 700));

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  auto* event_generator = GetEventGenerator();
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
  auto* capture_mode_session = controller->capture_mode_session();
  CaptureModeSessionTestApi test_api(capture_mode_session);

  const auto* preview_window =
      camera_controller->camera_preview_widget()->GetNativeWindow();

  // Tab to focus on `window2`, which is the most recently used window. It
  // should be set to the current selected window, and the camera preview should
  // be shown inside it.
  auto* camera_preview_view = camera_controller->camera_preview_view();
  auto* resize_button = GetPreviewResizeButton();
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(test_api.GetHighlightableWindow(window2.get())->has_focus());
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(window2.get(), preview_window->parent());

  // Press tab should advance to focus on the camera preview inside. But the
  // FocusGroup should not change, as the camera preview is treated as part of
  // the selected window in this case.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());
  // Press tab should focus on the resize button inside the camera preview.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(2u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_FALSE(camera_controller->camera_preview_view()->has_focus());

  // Press tab again should advance focus and set another window `window_` to be
  // the current selected window. The camera preview should be shown inside it
  // now.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(3u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(test_api.GetHighlightableWindow(window())->has_focus());
  EXPECT_EQ(window(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(window(), preview_window->parent());

  // Press tab should advance to focus on the camera preview that inside
  // `window_` now.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());
  // Press tab to advance the focus on the resize button inside the camera
  // preview.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(5u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_FALSE(camera_preview_view->has_focus());

  // Press tab again should advance to focus on the settings button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_FALSE(camera_preview_view->has_focus());

  // Shift tab when the settings button is focused should advance back to focus
  // on the resize button inside the camera preview. And the camera preview
  // should now be shown inside `window_`, which is the current selected window.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(5u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_EQ(window(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(window(), preview_window->parent());
  // Shift tab again should move the focus from the resize button to the camera
  // preview.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_TRUE(camera_preview_view->has_focus());

  // Shift tab again should focus on the `window_`
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(3u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(test_api.GetHighlightableWindow(window())->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_EQ(window(), capture_mode_session->GetSelectedWindow());

  // Continue shift tab should advance back to focus on the resize button inside
  // the camera preview. And the camera preview should now inside `window2`,
  // which is the current selected window.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(2u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());
  EXPECT_EQ(window2.get(), preview_window->parent());
  // Continue shift tab should move the focus from the resize button to the
  // camera preview.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_TRUE(camera_preview_view->has_focus());

  // Continue shift tab should focus on the `window2`.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(test_api.GetHighlightableWindow(window2.get())->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_EQ(window2.get(), capture_mode_session->GetSelectedWindow());

  // Continue shift tab should advance back to the window capture source button.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());

  // Destroy a window and test that the destroyed window will not be included in
  // the keyboard focus navigation.
  window2.reset();
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(test_api.GetHighlightableWindow(window())->has_focus());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(1u, test_api.GetCurrentFocusIndex());
  EXPECT_TRUE(camera_preview_view->has_focus());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(2u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_TRUE(resize_button->has_focus());
  // Continue tab after focusing on the resize button should advance the focus
  // to settings close button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
}

TEST_F(CaptureModeCameraTest,
       FocusableCameraPreviewInVideoRecordingWithFullscreenCapture) {
  UpdateDisplay("800x700");
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  auto* event_generator = GetEventGenerator();
  auto* camera_preview_view = camera_controller->camera_preview_view();
  auto* resize_button = GetPreviewResizeButton();
  StartVideoRecordingImmediately();

  EXPECT_TRUE(camera_preview_view->is_collapsible());
  EXPECT_FALSE(camera_controller->is_camera_preview_collapsed());

  // Press shortcut "Search+Alt+S" should focus the camera preview.
  SendKey(ui::VKEY_S, event_generator, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  EXPECT_TRUE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_FALSE(resize_button->GetVisible());

  // Press tab should fade in the resize button and advance the focus on it.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_TRUE(resize_button->GetVisible());

  // Shift tab should advance the focus back to the camera preview view. But the
  // resize button will be kept as visible as it will be fade out in a few
  // seconds.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_TRUE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_TRUE(resize_button->GetVisible());

  // Press tab again to focus on the resize button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_TRUE(resize_button->GetVisible());

  // Press space key when the resize button is focused should be able to expand
  // or collapse the camera preview.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_TRUE(camera_controller->is_camera_preview_collapsed());
  EXPECT_TRUE(resize_button->has_focus());
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_FALSE(camera_controller->is_camera_preview_collapsed());
  EXPECT_TRUE(resize_button->has_focus());

  // Press escape key should remove the focus from the camera preview, either
  // the camera preview view or the resize button.
  SendKey(ui::VKEY_ESCAPE, event_generator);
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());

  // Press shortcut "Search+Alt+S" should not focus the camera preview when
  // capture session is active even though video recording is in progress.
  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                         CaptureModeType::kImage);
  EXPECT_TRUE(controller->IsActive());
  EXPECT_TRUE(controller->is_recording_in_progress());
  SendKey(ui::VKEY_S, event_generator, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  EXPECT_FALSE(camera_preview_view->has_focus());

  // Press tab should still able to focus the camera preview view and the resize
  // button.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/5);
  EXPECT_TRUE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_TRUE(resize_button->has_focus());
  // Continue tab should be able to advance the focus on the settings button.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());

  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  EXPECT_TRUE(CaptureModeSessionFocusCycler::HighlightHelper::Get(
                  test_api.GetCaptureModeBarView()->settings_button())
                  ->has_focus());
}

TEST_F(CaptureModeCameraTest,
       FocusableCameraPreviewInVideoRecordingWithRegionCapture) {
  auto* controller = CaptureModeController::Get();
  controller->SetUserCaptureRegion(gfx::Rect(10, 10, 400, 550),
                                   /*by_user=*/true);

  StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  auto* event_generator = GetEventGenerator();
  auto* camera_preview_view = camera_controller->camera_preview_view();
  auto* resize_button = GetPreviewResizeButton();
  StartVideoRecordingImmediately();

  EXPECT_FALSE(camera_controller->is_camera_preview_collapsed());
  EXPECT_FALSE(camera_preview_view->is_collapsible());

  // Press shortcut "Search+Alt+S" should focus the camera preview.
  SendKey(ui::VKEY_S, event_generator, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  EXPECT_TRUE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_FALSE(resize_button->GetVisible());

  // Press tab nothing will happen. As the camera preview is not collapsible,
  // which means the camera preview is the only focusable item. Focus will not
  // be moved to the resize button and it will continue to be hidden.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_TRUE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
  EXPECT_FALSE(resize_button->GetVisible());

  // Mouse pressing outside of the camera preview should remove the focus from
  // the camera preview.
  const gfx::Point origin = camera_preview_view->GetBoundsInScreen().origin();
  const gfx::Vector2d delta(-50, -50);
  event_generator->MoveMouseTo(origin + delta);
  event_generator->ClickLeftButton();
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
}

TEST_F(CaptureModeCameraTest,
       FocusableCameraPreviewInVideoRecordingWithWindowCapture) {
  UpdateDisplay("1366x768");
  window()->SetBounds(gfx::Rect(30, 40, 800, 700));
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* event_generator = GetEventGenerator();
  auto* camera_preview_view = camera_controller->camera_preview_view();
  auto* resize_button = GetPreviewResizeButton();

  event_generator->MoveMouseTo(window()->GetBoundsInScreen().origin());
  EXPECT_EQ(controller->capture_mode_session()->GetSelectedWindow(), window());
  StartVideoRecordingImmediately();

  EXPECT_FALSE(camera_controller->is_camera_preview_collapsed());
  EXPECT_TRUE(camera_preview_view->is_collapsible());

  // Press shortcut "Search+Alt+S" should focus the camera preview.
  SendKey(ui::VKEY_S, event_generator, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
  EXPECT_TRUE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());

  // Press tab should fade in the resize button and advance the focus on it.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_FALSE(camera_preview_view->has_focus());
  EXPECT_TRUE(resize_button->has_focus());
  EXPECT_TRUE(resize_button->GetVisible());

  // Shift tab should advance the focus back to the camera preview view. But the
  // resize button will be kept as visible as it will be fade out in a few
  // seconds.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN);
  EXPECT_TRUE(camera_preview_view->has_focus());
  EXPECT_FALSE(resize_button->has_focus());
}

TEST_F(CaptureModeCameraTest, CaptureBarOpacityChangeOnKeyboardNavigation) {
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;

  // Update display size and update window with customized size to make sure
  // camera preview overlap with capture bar with capture source `kWindow`.
  UpdateDisplay("1366x768");
  window()->SetBounds({0, 0, 903, 700});

  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  auto* capture_session = controller->capture_mode_session();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  const auto* camera_preview_widget =
      camera_controller->camera_preview_widget();
  const auto* capture_bar_widget = capture_session->GetCaptureModeBarWidget();
  const ui::Layer* capture_bar_layer = capture_bar_widget->GetLayer();

  // Move mouse on top of `window` to set the selected window. Verify capture
  // bar is `kOverlapOpacity`.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(window()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(capture_session->GetSelectedWindow(), window());
  EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Now tab through the capture bar, verify that as long as the focus is on
  // capture bar or capture settings menu, capture bar is updated to full
  // opaque.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f);

  // Tab four times to focus the last source button (window mode button). Verify
  // capture bar is still fully opaque.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/4);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(4u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f);

  // Tab once to focus on the window to be captured, verify that capture bar is
  // `kOverlapOpacity` since the focus is removed from capture bar.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Tab three times to focus on the settings button, verify capture bar is
  // updated to fully opaque again.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/3);
  EXPECT_EQ(FocusGroup::kSettingsClose, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f);

  // Now enter space to open the settings menu. Verify capture bar is still full
  // opaque.
  SendKey(ui::VKEY_SPACE, event_generator);
  EXPECT_EQ(FocusGroup::kPendingSettings, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f);

  // Tab once to focus on the settings menu. Verify capture bar is still full
  // opaque.
  SendKey(ui::VKEY_TAB, event_generator);
  EXPECT_EQ(FocusGroup::kSettingsMenu, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f);
}

TEST_F(CaptureModeCameraTest, CaptureLabelOpacityChangeOnKeyboardNavigation) {
  UpdateDisplay("800x600");
  using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  CaptureModeSessionTestApi test_api(controller->capture_mode_session());
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* camera_preview_widget = camera_controller->camera_preview_widget();
  auto* capture_label_widget = capture_session->capture_label_widget();
  ui::Layer* capture_label_layer = capture_label_widget->GetLayer();

  // Select capture region to make sure capture label is overlapped with
  // camera preview. Verify capture label is `kOverlapOpacity`.
  const int min_region_length = kMinRegionLengthForCameraToIntersectLabelButton;
  SelectCaptureRegion({100, 100, min_region_length, min_region_length});
  EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects(
      camera_preview_widget->GetWindowBoundsInScreen()));
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  auto* event_generator = GetEventGenerator();
  // Tab four times to focus on the region capture source, verify capture label
  // is still `kOverlapOpacity`.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/4);
  EXPECT_EQ(FocusGroup::kTypeSource, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(3u, test_api.GetCurrentFocusIndex());
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Tab twice to focus on `kSelection`, verify capture label is still
  // `kOverlapOpacity`.
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/2);
  EXPECT_EQ(FocusGroup::kSelection, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);

  // Tab eleven times to focus on cpature label, verify capture label is updated
  // to fully opaque.
  EXPECT_FALSE(camera_controller->camera_preview_view()->is_collapsible());
  SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/10);
  EXPECT_EQ(FocusGroup::kCaptureButton, test_api.GetCurrentFocusGroup());
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.0f);

  // Tab until the settings button on the capture bar is focused, then verify
  // that the capture lable's opacity is updated to `kOverlapOpacity`.
  while (FocusGroup::kSettingsClose != test_api.GetCurrentFocusGroup()) {
    SendKey(ui::VKEY_TAB, event_generator);
  }
  EXPECT_EQ(capture_label_layer->GetTargetOpacity(),
            capture_mode::kCaptureUiOverlapOpacity);
}

// Tests that when switching capture source from `kRegion` to `kFullscreen`,
// camera preview should be shown.
// Regression test for https://crbug.com/1316911.
TEST_F(CaptureModeCameraTest, CameraPreviewVisibilityOnCaptureSourceChanged) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  AddDefaultCamera();
  CaptureModeTestApi().SelectCameraAtIndex(0);
  auto* camera_preview_widget = GetCameraController()->camera_preview_widget();
  auto* preview_window = camera_preview_widget->GetNativeWindow();

  // Verify that camera preview is visible.
  EXPECT_EQ(preview_window->parent(),
            preview_window->GetRootWindow()->GetChildById(
                kShellWindowId_MenuContainer));
  EXPECT_TRUE(camera_preview_widget->IsVisible());
  EXPECT_TRUE(preview_window->TargetVisibility());

  // Click on the region source button, verify that camera preview is parented
  // to UnparentedContainer and becomes invisible.
  auto* event_generator = GetEventGenerator();
  ClickOnView(GetRegionToggleButton(), event_generator);
  EXPECT_EQ(preview_window->parent(),
            preview_window->GetRootWindow()->GetChildById(
                kShellWindowId_UnparentedContainer));
  EXPECT_FALSE(camera_preview_widget->IsVisible());

  // Now switch capture source to `kFullscreen`, verify that camera preview is
  // parented to MenuContainer and becomes visible again.
  ClickOnView(GetFullscreenToggleButton(), event_generator);
  EXPECT_EQ(preview_window->parent(),
            preview_window->GetRootWindow()->GetChildById(
                kShellWindowId_MenuContainer));
  EXPECT_TRUE(preview_window->TargetVisibility());
  EXPECT_TRUE(camera_preview_widget->IsVisible());
}

// Tests that the recording starts with camera metrics are recorded correctly
// both in clamshell and tablet mode.
TEST_F(CaptureModeCameraTest, RecordingStartsWithCameraHistogramTest) {
  base::HistogramTester histogram_tester;
  constexpr char kHistogramNameBase[] = "RecordingStartsWithCamera";

  AddDefaultCamera();

  struct {
    bool tablet_enabled;
    bool camera_on;
  } kTestCases[] = {
      {false, false},
      {false, true},
      {true, false},
      {true, true},
  };

  for (const auto test_case : kTestCases) {
    if (test_case.tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    const std::string histogram_name =
        BuildHistogramName(kHistogramNameBase, /*behavior=*/nullptr,
                           /*append_ui_mode_suffix=*/true);
    histogram_tester.ExpectBucketCount(histogram_name, test_case.camera_on, 0);

    auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                           CaptureModeType::kVideo);
    auto* camera_controller = GetCameraController();
    camera_controller->SetSelectedCamera(
        test_case.camera_on ? CameraId(kDefaultCameraModelId, 1) : CameraId());

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

    controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
    WaitForCaptureFileToBeSaved();

    histogram_tester.ExpectBucketCount(histogram_name, test_case.camera_on, 1);
  }
}

// Tests that the number of camera disconnections happens during recording is
// recorded correctly both in clamshell and tablet mode.
TEST_F(CaptureModeCameraTest,
       RecordCameraDisconnectionsDuringRecordingsHistogramTest) {
  constexpr char kHistogramNameBase[] = "CameraDisconnectionsDuringRecordings";
  base::HistogramTester histogram_tester;

  auto* camera_controller = GetCameraController();

  auto disconnect_and_reconnect_camera_n_times = [&](int n) {
    for (int i = 0; i < n; i++) {
      AddAndRemoveCameraAndTriggerGracePeriod();
      camera_controller->camera_reconnect_timer_for_test()->FireNow();
    }
  };

  for (const bool tablet_enabled : {false, true}) {
    if (tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                           CaptureModeType::kVideo);
    controller->StartVideoRecordingImmediatelyForTesting();

    // Disconnect the camera, exhaust the timer and reconnect three times. The
    // metric should record accordingly.
    disconnect_and_reconnect_camera_n_times(3);
    controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
    WaitForCaptureFileToBeSaved();
    histogram_tester.ExpectBucketCount(
        BuildHistogramName(kHistogramNameBase, /*behavior=*/nullptr,
                           /*append_ui_mode_suffix=*/true),
        3, 1);
  }
}

// Tests that the number of connected cameras to the device is recorded whenever
// the number changes.
TEST_F(CaptureModeCameraTest, RecordNumberOfConnectedCamerasHistogramTest) {
  constexpr char kHistogramNameBase[] = "NumberOfConnectedCameras";
  const std::string histogram_name =
      BuildHistogramName(kHistogramNameBase, /*behavior=*/nullptr,
                         /*append_ui_mode_suffix=*/false);
  base::HistogramTester histogram_tester;
  // Make sure the device change alert triggered by the SystemMonitor is handled
  // before we connect a camera device.
  {
    base::RunLoop loop;
    GetCameraController()->SetOnCameraListReceivedForTesting(
        loop.QuitClosure());
    base::SystemMonitor::Get()->ProcessDevicesChanged(
        base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
    loop.Run();
  }

  // Verify that before we connect any camera device, there's 0 cameras and it
  // has been recorded.
  histogram_tester.ExpectBucketCount(histogram_name, 0, 1);

  // Connect one camera, verify that the number of one camera device has been
  // recorded once.
  AddFakeCamera("/dev/video", "fake cam ", "model 1");
  histogram_tester.ExpectBucketCount(histogram_name, 1, 1);

  // Connect the second camera, verify that the number of two camera devices has
  // been recorded once.
  AddFakeCamera("/dev/video1", "fake cam 2", "model 2");
  histogram_tester.ExpectBucketCount(histogram_name, 2, 1);

  // Disconnect the second camera, now the number of connected cameres drops
  // back to one, verify that the number of one camera device has been recorded
  // twice.
  RemoveFakeCamera("/dev/video1");
  histogram_tester.ExpectBucketCount(histogram_name, 1, 2);

  // Connect the third camera, now the number of connected cameras is two again,
  // verify that the number of two camera devices has been recorded twice.
  AddFakeCamera("/dev/video2", "fake cam 3", "model 3");
  histogram_tester.ExpectBucketCount(histogram_name, 2, 2);
}

// TODO(crbug.com/331316079): Flaky on LSAN / ASAN.
// TODO(crbug.com/350946974): Flaky in general.
// Tests that the duration for disconnected camera to become available again is
// recorded correctly both in clamshell and tablet mode.
TEST_F(CaptureModeCameraTest,
       DISABLED_RecordCameraReconnectDurationHistogramTest) {
  constexpr char kHistogramNameBase[] = "CameraReconnectDuration";
  base::HistogramTester histogram_tester;

  for (const bool tablet_enabled : {false, true}) {
    if (tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    AddAndRemoveCameraAndTriggerGracePeriod();
    WaitForSeconds(1);
    AddDefaultCamera();
    histogram_tester.ExpectBucketCount(
        BuildHistogramName(kHistogramNameBase, /*behavior=*/nullptr,
                           /*append_ui_mode_suffix=*/true),
        1, 1);
    RemoveDefaultCamera();
  }
}

// Tests that the camera size on start is recorded correctly in the metrics both
// in clamshell and tablet mode.
TEST_F(CaptureModeCameraTest, RecordingCameraSizeOnStartHistogramTest) {
  UpdateDisplay("1366x768");

  constexpr char kHistogramNameBase[] = "RecordingCameraSizeOnStart";
  base::HistogramTester histogram_tester;

  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  for (const bool tablet_enabled : {false, true}) {
    if (tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    const std::string histogram_name =
        BuildHistogramName(kHistogramNameBase, /*behavior=*/nullptr,
                           /*append_ui_mode_suffix=*/true);
    for (const bool collapsed : {false, true}) {
      const auto sample = collapsed ? CaptureModeCameraSize::kCollapsed
                                    : CaptureModeCameraSize::kExpanded;
      histogram_tester.ExpectBucketCount(histogram_name, sample, 0);

      auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                             CaptureModeType::kVideo);

      auto* event_generator = GetEventGenerator();

      // Resize button is hidden by default, click/tap on the preview to make
      // it visible.
      ClickOrTapView(camera_controller->camera_preview_view(), tablet_enabled,
                     event_generator);

      auto* resize_button = GetPreviewResizeButton();
      DCHECK(resize_button);

      if (collapsed) {
        if (!camera_controller->is_camera_preview_collapsed())
          ClickOrTapView(resize_button, tablet_enabled, event_generator);

        EXPECT_TRUE(camera_controller->is_camera_preview_collapsed());
      } else {
        if (camera_controller->is_camera_preview_collapsed())
          ClickOrTapView(resize_button, tablet_enabled, event_generator);

        EXPECT_FALSE(camera_controller->is_camera_preview_collapsed());
      }

      StartVideoRecordingImmediately();
      controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
      WaitForCaptureFileToBeSaved();
      histogram_tester.ExpectBucketCount(histogram_name, sample, 1);
    }
  }
}

// Tests that the camera position on start is recorded correctly in the metrics
// both in clamshell and tablet mode.
TEST_F(CaptureModeCameraTest, RecordingCameraPositionOnStartHistogramTest) {
  constexpr char kHistogramName[] = "RecordingCameraPositionOnStart";
  base::HistogramTester histogram_tester;

  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  AddDefaultCamera();
  auto* camera_controller = GetCameraController();
  const CameraId camera_id(kDefaultCameraModelId, 1);
  camera_controller->SetSelectedCamera(camera_id);

  const CameraPreviewSnapPosition kCameraPositionTestCases[]{
      CameraPreviewSnapPosition::kTopLeft,
      CameraPreviewSnapPosition::kBottomLeft,
      CameraPreviewSnapPosition::kTopRight,
      CameraPreviewSnapPosition::kBottomRight};

  for (const bool tablet_enabled : {false, true}) {
    if (tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    const std::string histogram_name = BuildHistogramName(
        kHistogramName, /*behavior=*/nullptr, /*append_ui_mode_suffix=*/true);
    for (const auto camera_position : kCameraPositionTestCases) {
      histogram_tester.ExpectBucketCount(histogram_name, camera_position, 0);
      auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                             CaptureModeType::kVideo);
      DCHECK(camera_controller->camera_preview_widget());
      camera_controller->SetCameraPreviewSnapPosition(camera_position);
      StartVideoRecordingImmediately();
      controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
      WaitForCaptureFileToBeSaved();
      histogram_tester.ExpectBucketCount(histogram_name, camera_position, 1);
    }
  }
}

TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnCaptureRegionUpdated) {
  UpdateDisplay("800x600");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  auto* capture_toast_controller = capture_session->capture_toast_controller();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Set capture region big enough to fit the camera preview. Verify the
  // current capture toast is `kUserNudge`.
  const gfx::Rect capture_region(100, 100, 300, 300);
  SelectCaptureRegion(capture_region);
  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  EXPECT_TRUE(capture_toast_widget);
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kUserNudge);

  // Update capture region small enough to not fit the camera preview. Verify
  // that the capture toast is updated to `kCameraPreview` and the user nudge is
  // dismissed forever.
  const int delta_x =
      capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 30 -
      capture_region.width();
  const int delta_y =
      capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 30 -
      capture_region.height();
  const gfx::Vector2d delta(delta_x, delta_y);
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(capture_region.bottom_right());
  event_generator->PressLeftButton();
  // Verify that when drag starts, the capture toast is hidden.
  EXPECT_FALSE(capture_toast_widget->IsVisible());
  event_generator->MoveMouseTo(capture_region.bottom_right() + delta);
  event_generator->ReleaseLeftButton();
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  EXPECT_FALSE(GetUserNudgeController());

  // Start dragging the capture region again to update it, but make it still
  // small enough to not fit the camera preview. Verify at the beginning of the
  // drag, preview toast is hidden. After the drag is released, preview toast is
  // shown again.
  const gfx::Vector2d delta1(delta_x + 10, delta_y + 10);
  event_generator->set_current_screen_location(capture_region.bottom_right());
  event_generator->PressLeftButton();
  // Verify that when drag starts, the capture toast is hidden.
  EXPECT_FALSE(capture_toast_widget->IsVisible());
  event_generator->MoveMouseTo(capture_region.bottom_right() + delta1);
  event_generator->ReleaseLeftButton();
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);
  EXPECT_TRUE(capture_toast_widget->IsVisible());

  // Update capture region big enough to show the camera preview. Verify the
  // preview toast is hidden.
  event_generator->set_current_screen_location(capture_region.origin());
  event_generator->PressLeftButton();
  // Verify that when drag starts, the capture toast is hidden.
  EXPECT_FALSE(capture_toast_widget->IsVisible());
  event_generator->MoveMouseTo(capture_region.bottom_right());
  event_generator->ReleaseLeftButton();
  // Verify that since the capture toast is dismissed, current toast type is
  // reset.
  EXPECT_FALSE(capture_toast_controller->current_toast_type());
  EXPECT_FALSE(capture_toast_widget->IsVisible());
}

// Tests that the capture toast will be faded out on time out when there are no
// actions taken.
TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnTimeOut) {
  UpdateDisplay("800x600");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  auto* capture_toast_controller = capture_session->capture_toast_controller();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Set capture region small enough to not fit the camera preview. Verify the
  // current capture toast is `kCameraPreview`.
  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
  SelectCaptureRegion(capture_region);
  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);

  // Verify the timer is running after the toast is shown and when the timer is
  // fired, the capture toast is hidden.
  base::OneShotTimer* timer =
      capture_toast_controller->capture_toast_dismiss_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());
  timer->FireNow();
  EXPECT_FALSE(capture_toast_widget->IsVisible());
}

TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnSettingsMenuOpen) {
  UpdateDisplay("800x600");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  auto* capture_toast_controller = capture_session->capture_toast_controller();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Set capture region small enough to not fit the camera preview. Verify the
  // current capture toast is `kCameraPreview`.
  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
  SelectCaptureRegion(capture_region);
  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);

  // Now open settings menu, verify that preview toast is dismissed immediately
  // on settings menu open.
  OpenSettingsView();
  EXPECT_FALSE(capture_toast_widget->IsVisible());
}

TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnCaptureRegionMoved) {
  UpdateDisplay("800x600");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  auto* capture_toast_controller = capture_session->capture_toast_controller();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Set capture region small enough to not fit the camera preview. Verify the
  // current capture toast is `kCameraPreview`.
  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
  SelectCaptureRegion(capture_region);
  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);

  // Start moving the capture region, verify the preview toast is hidden at the
  // beginning of the move and is shown once the move is done.
  const gfx::Vector2d delta(20, 20);
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(capture_region.origin() + delta);
  event_generator->PressLeftButton();
  EXPECT_FALSE(capture_toast_widget->IsVisible());
  event_generator->MoveMouseTo(capture_region.CenterPoint());
  event_generator->ReleaseLeftButton();
  EXPECT_TRUE(capture_toast_widget->IsVisible());
}

// Tests that the preview toast shows correctly when capture mode is turned on
// through quick settings which keeps the configurations) from the previous
// session.
TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnCaptureModeTurnedOn) {
  UpdateDisplay("800x600");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  auto* capture_toast_controller = capture_session->capture_toast_controller();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Set capture region small enough to not fit the camera preview. Verify the
  // current capture toast is `kCameraPreview`.
  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
  SelectCaptureRegion(capture_region);
  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);

  // Close capture mode.
  controller->Stop();

  // Turn on capture mode again through the quick settings, verify that toast
  // preview is visible.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  capture_toast_controller = capture_session->capture_toast_controller();
  EXPECT_TRUE(capture_toast_controller->capture_toast_widget()->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);
}

TEST_F(CaptureModeCameraTest, ToastStackingOrderChangeOnCaptureModeTurnedOn) {
  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Set capture region small enough to make capture toast shown.
  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
  SelectCaptureRegion(capture_region);

  // Close capture mode.
  controller->Stop();

  // Turn on capture mode again through the quick settings, verify that the
  // stacking order for capture toast relative to other capture UIs is correct.
  controller->Start(CaptureModeEntryType::kQuickSettings);
  base::RunLoop().RunUntilIdle();
  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* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  auto* capture_toast_window = capture_toast_widget->GetNativeWindow();
  auto* capture_label_window =
      capture_session->capture_label_widget()->GetNativeWindow();
  auto* capture_bar_window =
      capture_session->GetCaptureModeBarWidget()->GetNativeWindow();
  auto* camera_preview_window =
      camera_controller->camera_preview_widget()->GetNativeWindow();

  EXPECT_TRUE(
      IsWindowStackedRightBelow(capture_label_window, capture_bar_window));
  EXPECT_TRUE(
      IsWindowStackedRightBelow(capture_toast_window, capture_label_window));
  EXPECT_TRUE(
      IsWindowStackedRightBelow(camera_preview_window, capture_toast_window));
  EXPECT_TRUE(IsLayerStackedRightBelow(capture_session->layer(),
                                       camera_preview_window->layer()));
}

TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnPerformingCapture) {
  UpdateDisplay("800x600");

  auto* controller =
      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  auto* capture_session =
      static_cast<CaptureModeSession*>(controller->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  auto* camera_controller = GetCameraController();
  auto* capture_toast_controller = capture_session->capture_toast_controller();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Set capture region small enough to not fit the camera preview. Verify the
  // current capture toast is `kCameraPreview`.
  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
  SelectCaptureRegion(capture_region);
  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);

  // Perform the screen recording, verify that the capture toast is going to be
  // faded out.
  controller->PerformCapture();
  EXPECT_EQ(capture_toast_widget->GetLayer()->GetTargetOpacity(), 0.f);
}

TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnMultiDisplays) {
  UpdateDisplay("800x700,801+0-800x700");
  const gfx::Rect first_display_bounds(0, 0, 800, 700);
  const gfx::Rect second_display_bounds(801, 0, 800, 700);

  // Set the window's bounds small enough to not fit the camera preview.
  window()->SetBoundsInScreen(
      gfx::Rect(600, 500, 100, 100),
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          Shell::GetAllRootWindows()[0]));

  // Create a window in the second display and set its bounds small enough to
  // not fit the camera preview.
  std::unique_ptr<aura::Window> window1(CreateTestWindow());
  window1->SetBoundsInScreen(
      gfx::Rect(1400, 500, 100, 100),
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          Shell::GetAllRootWindows()[1]));

  // Start the capture session.
  auto* controller =
      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  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();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Move the mouse on top of `window` to select it, since it's too small to fit
  // the camera preview, verify the preview toast shows and it's on the first
  // display.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(window()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(capture_session->GetSelectedWindow(), window());
  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);
  first_display_bounds.Contains(
      capture_toast_widget->GetWindowBoundsInScreen());

  // Now move the mouse to the top of `window1`, since it's also too small to
  // fit the camera preview, verify the preview toast still shows. Since
  // `window1` is on the second display, verify the preview toast also shows up
  // on the second display.
  event_generator->MoveMouseTo(window1->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(capture_session->GetSelectedWindow(), window1.get());
  EXPECT_TRUE(capture_toast_widget->IsVisible());
  ASSERT_TRUE(capture_toast_controller->current_toast_type());
  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
            CaptureToastType::kCameraPreview);
  second_display_bounds.Contains(
      capture_toast_widget->GetWindowBoundsInScreen());

  // Move mouse to the outside of `window1`, verify that preview toast is
  // dismissed since there's no window selected for now.
  event_generator->MoveMouseTo({1300, 500});
  EXPECT_FALSE(capture_toast_widget->IsVisible());

  // Update the bounds of `window` big enough to fit the camera preview.
  window()->SetBoundsInScreen(
      gfx::Rect(100, 200, 300, 300),
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          Shell::GetAllRootWindows()[0]));

  // Now move the mouse to the top of `window` again, verify that preview toast
  // is not shown, since the window is big enough to show the camera preview.
  event_generator->MoveMouseTo(window()->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(capture_session->GetSelectedWindow(), window());
  EXPECT_FALSE(capture_toast_widget->IsVisible());
}

class CaptureModeCameraPreviewTest
    : public CaptureModeCameraTest,
      public testing::WithParamInterface<CaptureModeSource> {
 public:
  enum class CameraPreviewState {
    // The camera preview is shown inside an area that makes its expanded size
    // big enough so it can collapse to a smaller size.
    kCollapsible,

    // The camera preview is shown inside an area that is small enough to
    // disable its collapsability without causing it to hide.
    kNotCollapsible,

    // The camera preview is shown inside an area that is too small for it to
    // show at all.
    kHidden,
  };

  CaptureModeCameraPreviewTest() = default;
  CaptureModeCameraPreviewTest(const CaptureModeCameraPreviewTest&) = delete;
  CaptureModeCameraPreviewTest& operator=(const CaptureModeCameraPreviewTest&) =
      delete;
  ~CaptureModeCameraPreviewTest() override = default;

  void StartCaptureSessionWithParam() {
    auto* controller = CaptureModeController::Get();
    const gfx::Rect capture_region(10, 20, 1300, 750);
    controller->SetUserCaptureRegion(capture_region, /*by_user=*/true);
    // Set the window's bounds big enough here to make sure after display
    // rotation, the event is located on top of `window_`.
    // TODO(conniekxu): investigate why the position of the event received is
    // different than the position we pass.
    window()->SetBounds({30, 40, 1300, 750});

    StartCaptureSession(GetParam(), CaptureModeType::kVideo);
    if (GetParam() == CaptureModeSource::kWindow)
      GetEventGenerator()->MoveMouseToCenterOf(window());
  }

  gfx::Size GetMinSurfaceSizeForCollapsibleCamera() const {
    const int min_length = capture_mode::kMinCollapsibleCameraPreviewDiameter *
                           capture_mode::kCaptureSurfaceShortSideDivider;
    return gfx::Size(min_length, min_length);
  }

  gfx::Size GetMinSurfaceSizeSoCameraBecomes(
      CameraPreviewState preview_state) const {
    gfx::Size min_size = GetMinSurfaceSizeForCollapsibleCamera();
    switch (preview_state) {
      case CameraPreviewState::kCollapsible:
        min_size.Enlarge(10, 20);
        break;
      case CameraPreviewState::kNotCollapsible:
        min_size.Enlarge(-10, -20);
        break;
      case CameraPreviewState::kHidden:
        const int length_for_hidden =
            capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 5;
        min_size.SetSize(length_for_hidden, length_for_hidden - 5);
        break;
    }
    return min_size;
  }

  void ResizeDisplaySoCameraPreviewBecomes(CameraPreviewState preview_state) {
    gfx::Size min_size = GetMinSurfaceSizeSoCameraBecomes(preview_state);
    const int shelf_size = ShelfConfig::Get()->shelf_size();
    min_size.Enlarge(shelf_size, shelf_size);
    UpdateDisplay(min_size.ToString());
  }

  void ResizeRegionSoCameraPreviewBecomes(CameraPreviewState preview_state) {
    CaptureModeController::Get()->SetUserCaptureRegion(
        gfx::Rect(GetMinSurfaceSizeSoCameraBecomes(preview_state)),
        /*by_user=*/true);
  }

  void ResizeWindowSoCameraPreviewBecomes(CameraPreviewState preview_state) {
    auto size = GetMinSurfaceSizeSoCameraBecomes(preview_state);
    if (auto* frame_header =
            capture_mode_util::GetWindowFrameHeader(window())) {
      size.Enlarge(0, frame_header->GetHeaderHeight());
    }
    window()->SetBounds(gfx::Rect(size));
  }

  void ResizeSurfaceSoCameraPreviewBecomes(CameraPreviewState preview_state) {
    switch (GetParam()) {
      case CaptureModeSource::kFullscreen:
        ResizeDisplaySoCameraPreviewBecomes(preview_state);
        break;
      case CaptureModeSource::kRegion:
        ResizeRegionSoCameraPreviewBecomes(preview_state);
        break;
      case CaptureModeSource::kWindow:
        ResizeWindowSoCameraPreviewBecomes(preview_state);
        break;
    }
  }

  // Based on the `CaptureModeSource`, it returns the current capture region's
  // bounds in screen.
  gfx::Rect GetCaptureBoundsInScreen() const {
    auto* controller = CaptureModeController::Get();
    auto* root = GetCurrentRoot();

    switch (GetParam()) {
      case CaptureModeSource::kFullscreen:
        return display::Screen::GetScreen()
            ->GetDisplayNearestWindow(root)
            .work_area();

      case CaptureModeSource::kRegion: {
        auto* recording_watcher =
            controller->video_recording_watcher_for_testing();
        gfx::Rect capture_region =
            controller->is_recording_in_progress()
                ? recording_watcher->GetEffectivePartialRegionBounds()
                : controller->user_capture_region();
        wm::ConvertRectToScreen(root, &capture_region);
        return capture_region;
      }

      case CaptureModeSource::kWindow:
        auto bounds =
            capture_mode_util::GetCaptureWindowConfineBounds(window());
        wm::ConvertRectToScreen(window(), &bounds);
        return bounds;
    }
  }

  gfx::Size GetExpectedPreviewSize(bool collapsed) const {
    return capture_mode_util::CalculateCameraPreviewSizeSpecs(
               GetCaptureBoundsInScreen().size(), collapsed)
        .size;
  }

  // Returns the cursor type when cursor is on top of the current capture
  // surface.
  ui::mojom::CursorType GetCursorTypeOnCaptureSurface() const {
    DCHECK(CaptureModeController::Get()->IsActive());

    switch (GetParam()) {
      case CaptureModeSource::kFullscreen:
      case CaptureModeSource::kWindow:
        return ui::mojom::CursorType::kCustom;
      case CaptureModeSource::kRegion:
        return ui::mojom::CursorType::kMove;
    }
  }
};

TEST_P(CaptureModeCameraPreviewTest, PreviewVisibilityWhileFolderSelection) {
  AddDefaultCamera();
  StartCaptureSessionWithParam();
  CaptureModeTestApi().SelectCameraAtIndex(0);

  // The camera preview should be initially visible.
  auto* controller = CaptureModeController::Get();
  ASSERT_TRUE(controller->IsActive());
  auto* preview_widget = GetCameraController()->camera_preview_widget();
  ASSERT_TRUE(preview_widget);
  EXPECT_TRUE(preview_widget->IsVisible());

  // Click on the settings button, the settings menu should open, and the camera
  // preview should remain visible.
  CaptureModeSessionTestApi session_test_api(
      controller->capture_mode_session());
  auto* settings_button =
      session_test_api.GetCaptureModeBarView()->settings_button();
  auto* event_generator = GetEventGenerator();
  ClickOnView(settings_button, event_generator);
  ASSERT_TRUE(session_test_api.GetCaptureModeSettingsWidget());
  EXPECT_TRUE(preview_widget->IsVisible());

  // Click on the "Select folder ..." option, the folder selection dialog should
  // open, all capture UIs should hide, including the camera preview.
  CaptureModeSettingsTestApi settings_test_api;
  ClickOnView(settings_test_api.GetSelectFolderMenuItem(), event_generator);
  EXPECT_TRUE(session_test_api.IsFolderSelectionDialogShown());
  EXPECT_FALSE(session_test_api.AreAllUisVisible());
  EXPECT_FALSE(preview_widget->IsVisible());

  // Dismiss the folder selection dialog, all capture UIs should show again,
  // including the camera preview.
  FakeFolderSelectionDialogFactory::Get()->CancelDialog();
  EXPECT_FALSE(session_test_api.IsFolderSelectionDialogShown());
  EXPECT_TRUE(session_test_api.AreAllUisVisible());
  EXPECT_TRUE(preview_widget->IsVisible());
}

// Tests that camera preview's bounds is updated after display rotations with
// two use cases, when capture session is active and when there's a video
// recording in progress.
TEST_P(CaptureModeCameraPreviewTest, DisplayRotation) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Verify that the camera preview should be at the bottom right corner of
  // capture bounds.
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Rotate the primary display by 90 degrees. Verify that the camera preview
  // is still at the bottom right corner of capture bounds.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(), display::Display::ROTATE_90,
      display::Display::RotationSource::USER);
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Start video recording, verify camera preview's bounds is updated after
  // display rotations when there's a video recording in progress.
  StartVideoRecordingImmediately();
  EXPECT_FALSE(CaptureModeController::Get()->IsActive());

  // Rotate the primary display by 180 degrees. Verify that the camera preview
  // is still at the bottom right corner of capture bounds.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(),
      display::Display::ROTATE_180, display::Display::RotationSource::USER);
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Rotate the primary display by 270 degrees. Verify that the camera preview
  // is still at the bottom right corner of capture bounds.
  Shell::Get()->display_manager()->SetDisplayRotation(
      WindowTreeHostManager::GetPrimaryDisplayId(),
      display::Display::ROTATE_270, display::Display::RotationSource::USER);
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());
}

// Tests that when camera preview is being dragged, at the end of the drag, it
// should be snapped to the correct snap position. It tests two use cases,
// when capture session is active and when there's a video recording in
// progress including drag to snap by mouse and by touch.
TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDragToSnap) {
  UpdateDisplay("1600x800");
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();

  // Verify that by default the snap position should be `kBottomRight` and
  // camera preview is placed at the correct position.
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Drag the camera preview for a small distance. Tests that even though the
  // snap position does not change, the preview should be snapped back to its
  // previous position.
  DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20,
                                      capture_bounds_center_point.y() + 20});
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Drag and drop camera preview by mouse to the top right of the
  // `capture_bounds_center_point`, verify that camera preview is snapped to
  // the top right with correct position.
  DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20,
                                      capture_bounds_center_point.y() - 20});
  EXPECT_EQ(CameraPreviewSnapPosition::kTopRight,
            camera_controller->camera_preview_snap_position());
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Now drag and drop camera preview by touch to the top left of the center
  // point, verify that camera preview is snapped to the top left with correct
  // position.
  DragPreviewToPoint(preview_widget,
                     {capture_bounds_center_point.x() - 20,
                      capture_bounds_center_point.y() - 20},
                     /*by_touch_gestures=*/true);
  EXPECT_EQ(CameraPreviewSnapPosition::kTopLeft,
            camera_controller->camera_preview_snap_position());
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Start video recording, verify camera preview is snapped to the correct
  // snap position at the end of drag when there's a video recording in
  // progress.
  StartVideoRecordingImmediately();
  EXPECT_FALSE(CaptureModeController::Get()->IsActive());

  // Drag and drop camera preview by mouse to the bottom left of the center
  // point, verify that camera preview is snapped to the bottom left with
  // correct position.
  DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() - 20,
                                      capture_bounds_center_point.y() + 20});
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomLeft,
            camera_controller->camera_preview_snap_position());
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  // Now drag and drop camera preview by touch to the bottom right of the
  // center point, verify that camera preview is snapped to the bottom right
  // with correct position.
  DragPreviewToPoint(preview_widget,
                     {capture_bounds_center_point.x() + 20,
                      capture_bounds_center_point.y() + 20},
                     /*by_touch_gestures=*/true);
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());
}

// Tests the use case after pressing on the resize button on camera preview and
// releasing the press outside of camera preview, camera preview is still
// draggable. Regression test for https://crbug.com/1308885.
TEST_P(CaptureModeCameraPreviewTest,
       CameraPreviewDragToSnapAfterPressOnResizeButton) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  auto* resize_button = GetPreviewResizeButton();
  const int camera_previw_width =
      preview_widget->GetWindowBoundsInScreen().width();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();
  const gfx::Point center_point_of_resize_button =
      resize_button->GetBoundsInScreen().CenterPoint();

  // By default the snap position of preview widget should be `kBottomRight`.
  EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
            camera_controller->camera_preview_snap_position());

  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(center_point_of_resize_button);
  event_generator->PressLeftButton();

  const gfx::Vector2d delta(-camera_previw_width, -camera_previw_width);
  // Now move mouse to the outside of the camera preview and then release.
  event_generator->MoveMouseTo(center_point_of_resize_button + delta);
  event_generator->ReleaseLeftButton();

  // Now try to drag the camera preview to the top left, after camera preview is
  // snapped, the current snap position should be `kTopLeft`.
  DragPreviewToPoint(preview_widget, capture_bounds_center_point + delta);
  EXPECT_EQ(CameraPreviewSnapPosition::kTopLeft,
            camera_controller->camera_preview_snap_position());
}

TEST_P(CaptureModeCameraPreviewTest, CaptureUisVisibilityChangeOnDragAndDrop) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  auto* capture_session = static_cast<CaptureModeSession*>(
      CaptureModeController::Get()->capture_mode_session());
  ASSERT_EQ(capture_session->session_type(), SessionType::kReal);
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point center_point_of_preview_widget =
      preview_widget->GetWindowBoundsInScreen().CenterPoint();

  const auto* capture_bar_widget = capture_session->GetCaptureModeBarWidget();
  const auto* capture_label_widget = capture_session->capture_label_widget();

  // Press on top of the preview widget. Verify capture bar and capture label
  // are hidden.
  auto* event_generator = GetEventGenerator();
  event_generator->set_current_screen_location(center_point_of_preview_widget);
  event_generator->PressLeftButton();
  EXPECT_FALSE(capture_bar_widget->IsVisible());
  EXPECT_FALSE(capture_label_widget->IsVisible());

  // Now drag and move the preview widget. Verify capture bar and capture
  // label are still hidden.
  const gfx::Vector2d delta(-50, -60);
  event_generator->MoveMouseTo(center_point_of_preview_widget + delta);
  EXPECT_FALSE(capture_bar_widget->IsVisible());
  EXPECT_FALSE(capture_label_widget->IsVisible());

  // Release the drag. Verify capture bar and capture label are shown again.
  event_generator->ReleaseLeftButton();
  EXPECT_TRUE(capture_bar_widget->IsVisible());
  EXPECT_TRUE(capture_label_widget->IsVisible());
}

TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDragToSnapOnMultipleDisplay) {
  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);

  // Start capture mode on the second display.
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();

  // Drag and drop camera preview by mouse to the top right of the
  // `capture_bounds_center_point`, verify that camera preview is snapped to
  // the top right with correct position.
  DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20,
                                      capture_bounds_center_point.y() - 20});
  EXPECT_EQ(CameraPreviewSnapPosition::kTopRight,
            camera_controller->camera_preview_snap_position());
  VerifyPreviewAlignment(GetCaptureBoundsInScreen());
}

// Tests that when there's a video recording is in progress, start a new
// capture session will make camera preview not draggable.
TEST_P(CaptureModeCameraPreviewTest,
       DragPreviewInNewCaptureSessionWhileVideoRecordingInProgress) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();

  StartVideoRecordingImmediately();
  EXPECT_FALSE(CaptureModeController::Get()->IsActive());
  // Start a new capture session while a video recording is in progress.
  auto* controller = CaptureModeController::Get();
  controller->Start(CaptureModeEntryType::kQuickSettings);

  const gfx::Rect preview_bounds_in_screen_before_drag =
      preview_widget->GetWindowBoundsInScreen();
  const auto snap_position_before_drag =
      camera_controller->camera_preview_snap_position();
  // Verify by default snap position is `kBottomRight`.
  EXPECT_EQ(snap_position_before_drag, CameraPreviewSnapPosition::kBottomRight);

  // Try to drag camera preview by mouse without dropping it, verify camera
  // preview is not draggable and its position is not changed.
  DragPreviewToPoint(preview_widget,
                     {preview_bounds_in_screen_before_drag.x() + 20,
                      preview_bounds_in_screen_before_drag.y() + 20},
                     /*by_touch_gestures=*/false,
                     /*drop=*/false);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(),
            preview_bounds_in_screen_before_drag);

  // Try to drag and drop camera preview by touch to the top left of the
  // current capture bounds' center point, verity it's not moved. Also verify
  // the snap position is not updated.
  DragPreviewToPoint(preview_widget,
                     {capture_bounds_center_point.x() - 20,
                      capture_bounds_center_point.y() - 20},
                     /*by_touch_gestures=*/true);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(),
            preview_bounds_in_screen_before_drag);
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            snap_position_before_drag);
}

// Tests that the bounds of the camera preview widget should always be
// constrained by the capture mode confine bounds.
TEST_P(CaptureModeCameraPreviewTest,
       PreviewWidgetIsConstrainedByConfineBounds) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  ASSERT_TRUE(preview_widget);

  const auto confine_bounds = GetCaptureBoundsInScreen();

  // Create an outsetted bounds to generate locations outside of the confine
  // bounds.
  gfx::Rect outer_rect = confine_bounds;
  outer_rect.Inset(-20);

  for (const auto& release_point :
       {outer_rect.origin(), outer_rect.top_right(), outer_rect.bottom_left(),
        outer_rect.bottom_right()}) {
    DragPreviewToPoint(preview_widget, release_point);
    EXPECT_TRUE(
        confine_bounds.Contains(preview_widget->GetWindowBoundsInScreen()));
  }
}

// Tests that dragging camera preview outside of the preview circle shouldn't
// work even if the drag events are contained in the preview bounds.
TEST_P(CaptureModeCameraPreviewTest, DragPreviewOutsidePreviewCircle) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();
  const gfx::Rect preview_bounds_in_screen_before_drag =
      preview_widget->GetWindowBoundsInScreen();

  // Try to drag camera preview at its origin point, verify camera
  // preview is not draggable and its position is not changed.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(preview_bounds_in_screen_before_drag.origin());
  event_generator->PressLeftButton();
  event_generator->MoveMouseTo(capture_bounds_center_point);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(),
            preview_bounds_in_screen_before_drag);
}

// Tests that dragging camera preview outside of the preview circle doesn't
// work when video recording is in progress.
TEST_P(CaptureModeCameraPreviewTest,
       DragPreviewOutsidePreviewCircleWhileVideoRecordingInProgress) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();

  const gfx::Rect preview_bounds_in_screen_before_drag =
      preview_widget->GetWindowBoundsInScreen();
  const auto snap_position_before_drag =
      camera_controller->camera_preview_snap_position();
  // Verify by default snap position is `kBottomRight`.
  EXPECT_EQ(snap_position_before_drag, CameraPreviewSnapPosition::kBottomRight);

  // Try to drag camera preview at its origin point to the top left of current
  // capture bounds' center point, verity it's not moved.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(preview_bounds_in_screen_before_drag.origin());
  event_generator->PressLeftButton();
  event_generator->MoveMouseTo(capture_bounds_center_point);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(),
            preview_bounds_in_screen_before_drag);

  // Release drag, verify snap position is not changed.
  event_generator->ReleaseLeftButton();
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            snap_position_before_drag);
}

// Tests that when mouse event is on top of camera preview circle, cursor type
// should be updated accordingly.
TEST_P(CaptureModeCameraPreviewTest, CursorTypeUpdates) {
  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Rect preview_bounds_in_screen =
      preview_widget->GetWindowBoundsInScreen();
  const gfx::Point camera_preview_center_point =
      preview_bounds_in_screen.CenterPoint();
  const gfx::Point camera_preview_origin_point =
      preview_bounds_in_screen.origin();
  auto* event_generator = GetEventGenerator();

  auto* cursor_manager = Shell::Get()->cursor_manager();
  // Verify that moving mouse to the origin point on camera preview won't
  // update the cursor type to `kPointer`.
  event_generator->MoveMouseTo(preview_bounds_in_screen.origin());
  EXPECT_NE(cursor_manager->GetCursor(), ui::mojom::CursorType::kPointer);

  // Verify that moving mouse on camera preview will update the cursor type
  // to `kPointer`.
  event_generator->MoveMouseTo(camera_preview_center_point);
  EXPECT_EQ(cursor_manager->GetCursor(), ui::mojom::CursorType::kPointer);

  // Move mouse from camera preview to capture surface, verify cursor type is
  // updated to the correct type of the current capture source.
  event_generator->MoveMouseTo({camera_preview_origin_point.x() - 10,
                                camera_preview_origin_point.y() - 10});
  EXPECT_EQ(cursor_manager->GetCursor(), GetCursorTypeOnCaptureSurface());

  // Drag camera preview, verify that cursor type is updated to `kPointer`.
  DragPreviewToPoint(preview_widget,
                     {camera_preview_center_point.x() - 10,
                      camera_preview_center_point.y() - 10},
                     /*by_touch_gestures=*/false,
                     /*drop=*/false);
  EXPECT_EQ(cursor_manager->GetCursor(), ui::mojom::CursorType::kPointer);

  // Continue dragging and then drop camera preview, make sure cursor's
  // position is outside of camera preview after it's snapped. Verify cursor
  // type is updated to the correct type of the current capture source.
  DragPreviewToPoint(preview_widget, {camera_preview_origin_point.x() - 20,
                                      camera_preview_origin_point.y() - 20});
  EXPECT_EQ(cursor_manager->GetCursor(), GetCursorTypeOnCaptureSurface());
}

// Tests the functionality of resize button on changing the size of the camera
// preview widget, updating the icon image and tooltip text after clicking on
// it. It also tests the ability to restore to previous resize button settings
// if any when initiating a new capture mode session.
TEST_P(CaptureModeCameraPreviewTest, ResizePreviewWidget) {
  UpdateDisplay("800x700");
  StartCaptureSessionWithParam();
  auto* controller = CaptureModeController::Get();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  views::Widget* preview_widget = camera_controller->camera_preview_widget();
  DCHECK(preview_widget);
  const auto default_preview_bounds = preview_widget->GetWindowBoundsInScreen();
  EXPECT_EQ(default_preview_bounds.size(),
            GetExpectedPreviewSize(/*collapsed=*/false));

  auto* resize_button = GetPreviewResizeButton();
  auto* event_generator = GetEventGenerator();

  // Tests the default settings of the resize button.
  VerifyResizeButton(camera_controller->is_camera_preview_collapsed(),
                     resize_button);

  // First time click on resize button will make the preview widget collapse
  // to half of the default size with tooltip text and resize button icon
  // changed to expanded related contents accordingly.
  ClickOnView(resize_button, event_generator);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen().size(),
            GetExpectedPreviewSize(/*collapsed=*/true));
  VerifyResizeButton(camera_controller->is_camera_preview_collapsed(),
                     resize_button);

  // Second time click on resize button will make the preview widget expand
  // back to the default size with tooltip text and resize button icon changed
  // to the collapsed related contents accordingly.
  ClickOnView(resize_button, event_generator);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(), default_preview_bounds);
  VerifyResizeButton(camera_controller->is_camera_preview_collapsed(),
                     resize_button);

  // Click on the resize button again will collapse the preview widget. Exit the
  // session and start a new session, the settings for preview widget bounds and
  // resize button will be restored.
  ClickOnView(resize_button, event_generator);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen().size(),
            GetExpectedPreviewSize(/*collapsed=*/true));
  VerifyResizeButton(camera_controller->is_camera_preview_collapsed(),
                     resize_button);
  const auto collapsed_preview_bounds =
      preview_widget->GetWindowBoundsInScreen();
  controller->Stop();

  StartCaptureSessionWithParam();
  preview_widget = camera_controller->camera_preview_widget();
  EXPECT_TRUE(preview_widget);
  EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(),
            collapsed_preview_bounds);

  resize_button = GetPreviewResizeButton();
  EXPECT_TRUE(resize_button);
  VerifyResizeButton(camera_controller->is_camera_preview_collapsed(),
                     resize_button);
}

// Tests that resizing the camera preview using the resize button, which uses
// the bounds animation, works correctly on a secondary display. Regression test
// for https://crbug.com/1313247.
TEST_P(CaptureModeCameraPreviewTest, MultiDisplayResize) {
  UpdateDisplay("800x700,801+0-800x700");
  ASSERT_EQ(2u, Shell::GetAllRootWindows().size());

  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  // Put the cursor in the secondary display, and expect the session root to be
  // there.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(gfx::Point(900, 500));
  StartCaptureSessionWithParam();
  auto* controller = CaptureModeController::Get();
  auto* session = controller->capture_mode_session();
  auto* display_2_root = Shell::GetAllRootWindows()[1].get();

  // When capturing a window, set its bounds such that it is placed on the
  // secondary display.
  if (GetParam() == CaptureModeSource::kWindow) {
    views::Widget::GetWidgetForNativeWindow(window())->SetBounds(
        {900, 10, 700, 650});
    EXPECT_EQ(display_2_root, window()->GetRootWindow());
    event_generator->MoveMouseToCenterOf(window());
  }

  EXPECT_EQ(display_2_root, session->current_root());

  VerifyPreviewAlignment(GetCaptureBoundsInScreen());

  auto* resize_button = GetPreviewResizeButton();
  ClickOnView(resize_button, event_generator);

  VerifyPreviewAlignment(GetCaptureBoundsInScreen());
}

// Tests the visibility of the resize button on mouse events.
TEST_P(CaptureModeCameraPreviewTest, ResizeButtonVisibilityOnMouseEvents) {
  UpdateDisplay("1366x768");

  StartCaptureSessionWithParam();
  CaptureModeCameraController* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  views::Widget* preview_widget = camera_controller->camera_preview_widget();
  DCHECK(preview_widget);
  const gfx::Rect default_preview_bounds =
      preview_widget->GetWindowBoundsInScreen();

  CameraPreviewResizeButton* resize_button = GetPreviewResizeButton();
  auto* event_generator = GetEventGenerator();

  // Tests that the resize button is hidden by default.
  EXPECT_FALSE(resize_button->GetVisible());

  // Tests that the resize button will show up when the mouse is entering the
  // bounds of the preview widget.
  event_generator->MoveMouseTo(default_preview_bounds.CenterPoint());
  EXPECT_TRUE(resize_button->GetVisible());

  // Tests that the resize button will stay visible while moving the mouse
  // within the bounds of the preview widget.
  event_generator->MoveMouseTo(default_preview_bounds.top_center());
  EXPECT_TRUE(resize_button->GetVisible());

  // Tests that when the mouse is exiting the bounds of the preview widget, the
  // resize button will disappear after the predefined duration.
  auto outside_point = default_preview_bounds.origin();
  outside_point.Offset(-1, -1);
  event_generator->MoveMouseTo(outside_point);

  base::OneShotTimer* timer = camera_controller->camera_preview_view()
                                  ->resize_button_hide_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());
  EXPECT_EQ(timer->GetCurrentDelay(), capture_mode::kResizeButtonShowDuration);

  {
    ViewVisibilityChangeWaiter waiter(resize_button);
    EXPECT_TRUE(resize_button->GetVisible());
    timer->FireNow();
    waiter.Wait();
    EXPECT_FALSE(resize_button->GetVisible());
  }
}

// Tests the visibility of the resize button on tap events.
TEST_P(CaptureModeCameraPreviewTest, ResizeButtonVisibilityOnTapEvents) {
  UpdateDisplay("800x700");
  StartCaptureSessionWithParam();
  CaptureModeCameraController* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  views::Widget* preview_widget = camera_controller->camera_preview_widget();
  DCHECK(preview_widget);
  const gfx::Rect default_preview_bounds =
      preview_widget->GetWindowBoundsInScreen();

  CameraPreviewResizeButton* resize_button = GetPreviewResizeButton();
  auto* event_generator = GetEventGenerator();

  // Tests that the resize button is hidden by default.
  EXPECT_FALSE(resize_button->GetVisible());

  // Tests that resize button shows up when tapping within the bounds of the
  // preview widget and will fade out after the predefined duration.
  event_generator->GestureTapAt(default_preview_bounds.CenterPoint());
  EXPECT_TRUE(resize_button->GetVisible());
  base::OneShotTimer* timer = camera_controller->camera_preview_view()
                                  ->resize_button_hide_timer_for_test();
  EXPECT_TRUE(timer->IsRunning());
  EXPECT_EQ(timer->GetCurrentDelay(), capture_mode::kResizeButtonShowDuration);

  {
    ViewVisibilityChangeWaiter waiter(resize_button);
    timer->FireNow();
    waiter.Wait();
    EXPECT_FALSE(resize_button->GetVisible());
  }
}

// Tests the visibility of the resize button on camera preview drag to snap.
TEST_P(CaptureModeCameraPreviewTest,
       ResizeButtonVisibilityOnCameraPreviewDragToSnap) {
  UpdateDisplay("1366x768");
  StartCaptureSessionWithParam();
  CaptureModeCameraController* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  views::Widget* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Rect preview_bounds = preview_widget->GetWindowBoundsInScreen();

  CameraPreviewResizeButton* resize_button = GetPreviewResizeButton();
  auto* event_generator = GetEventGenerator();

  // Tests that the resize button is hidden by default.
  EXPECT_FALSE(resize_button->GetVisible());

  // Tests that the resize button will show up when the mouse is entering the
  // bounds of the preview widget.
  event_generator->MoveMouseTo(preview_bounds.CenterPoint());
  EXPECT_TRUE(resize_button->GetVisible());

  // Tests that when start dragging camera preview, resize button will be
  // hidden.
  event_generator->PressLeftButton();
  EXPECT_FALSE(resize_button->GetVisible());

  // Drag camera preview, test that resize button is still hidden.
  event_generator->MoveMouseBy(-300, -300);
  EXPECT_FALSE(resize_button->GetVisible());

  // Now release drag, verify that resize button is still hidden since cursor is
  // not on top of camera preview after it's snapped.
  event_generator->ReleaseLeftButton();
  EXPECT_FALSE(resize_button->GetVisible());

  // Now drag camera preview with a small distance, make sure when it's snapped
  // cursor is still on top of it. Verify that resize button is shown after
  // camera preview is snapped.
  const gfx::Vector2d delta(-30, -30);
  DragPreviewToPoint(preview_widget, preview_bounds.CenterPoint() + delta);
  EXPECT_TRUE(resize_button->GetVisible());
}

TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDeintersectsWithSystemTray) {
  UpdateDisplay("1366x768");

  // Open system tray.
  ui::test::EventGenerator* event_generator = GetEventGenerator();

  auto* system_tray = GetPrimaryUnifiedSystemTray();
  event_generator->MoveMouseTo(system_tray->GetBoundsInScreen().CenterPoint());
  event_generator->ClickLeftButton();
  EXPECT_TRUE(system_tray->IsBubbleShown());

  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  // Verify current default snap position is the `kBottomRight` before we select
  // a camera device.
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);

  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));

  auto* preview_widget = camera_controller->camera_preview_widget();
  EXPECT_TRUE(system_tray->IsBubbleShown());

  // Verify that camera preview doesn't overlap with system tray when it's
  // shown. Also verify current snap position is updated and not `kBottomRight`
  // anymore.
  EXPECT_FALSE(system_tray->GetBubbleBoundsInScreen().Intersects(
      preview_widget->GetWindowBoundsInScreen()));
  EXPECT_NE(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
}

TEST_P(CaptureModeCameraPreviewTest,
       CameraPreviewDeintersectsWithSystemTrayWhileVideoRecordingInProgress) {
  // Update display size big enough to make sure when capture source is
  // `kWindow`, the selected window is not system tray.
  UpdateDisplay("1366x768");

  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();

  StartVideoRecordingImmediately();
  // Verify current snap position is `kBottomRight`;
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
  // Now open system tray.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  auto* system_tray = GetPrimaryUnifiedSystemTray();
  event_generator->MoveMouseTo(system_tray->GetBoundsInScreen().CenterPoint());
  event_generator->ClickLeftButton();
  EXPECT_TRUE(system_tray->IsBubbleShown());

  // Verify that after system tray is open, camera preview is snapped and
  // doesn't overlap with system tray.
  EXPECT_NE(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
  EXPECT_FALSE(system_tray->GetBubbleBoundsInScreen().Intersects(
      preview_widget->GetWindowBoundsInScreen()));

  // Now try to drag camera preview to the bottom right corner, verify that
  // since system tray is still open, when drag is released, camera preview is
  // not snapped to the bottom right corner even it's the nearest corner to the
  // release position if system tray is still shown.
  const gfx::Vector2d delta(20, 20);
  DragPreviewToPoint(preview_widget, capture_bounds_center_point + delta);
  // Please notice, when capture source is `kWindow`, once the drag starts,
  // system tray will be closed, in this use case we just need to verify camera
  // preview is snapped to the bottom right corner.
  if (system_tray->IsBubbleShown()) {
    EXPECT_NE(camera_controller->camera_preview_snap_position(),
              CameraPreviewSnapPosition::kBottomRight);
    EXPECT_FALSE(system_tray->GetBubbleBoundsInScreen().Intersects(
        preview_widget->GetWindowBoundsInScreen()));
  } else {
    EXPECT_EQ(camera_controller->camera_preview_snap_position(),
              CameraPreviewSnapPosition::kBottomRight);
  }
}

TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDeintersectsWithPipWindow) {
  // Create a window at the bottom right of the display, then convert it to a
  // PIP window.
  std::unique_ptr<aura::Window> pip_window(
      CreateTestWindow(gfx::Rect(700, 450, 104, 100)));
  ConvertToPipWindow(pip_window.get());
  const gfx::Rect origin_pip_window_bounds = pip_window->GetBoundsInScreen();

  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();

  // Verify that after preview widget is enabled, pip window is repositioned to
  // avoid the overlap with camera preview.
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
  const gfx::Rect current_pip_window_bounds = pip_window->GetBoundsInScreen();
  EXPECT_NE(origin_pip_window_bounds, current_pip_window_bounds);
  EXPECT_FALSE(current_pip_window_bounds.Intersects(
      preview_widget->GetWindowBoundsInScreen()));
}

TEST_P(CaptureModeCameraPreviewTest,
       CameraPreviewDeintersectsWithPipWindowDuringRecording) {
  // Create a window at the top left of the display, then convert it to a PIP
  // window.
  std::unique_ptr<aura::Window> pip_window(
      CreateTestWindow(gfx::Rect(0, 0, 104, 100)));
  ConvertToPipWindow(pip_window.get());
  const gfx::Rect origin_pip_window_bounds = pip_window->GetBoundsInScreen();

  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();
  const gfx::Point capture_bounds_center_point =
      GetCaptureBoundsInScreen().CenterPoint();

  // Verify camera preview is enabled, current snap position is `kBottomRight`
  // and pip window is not repositioned since there's no overlap.
  EXPECT_TRUE(preview_widget);
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
  EXPECT_EQ(origin_pip_window_bounds, pip_window->GetBoundsInScreen());

  StartVideoRecordingImmediately();
  // Now drag camera preview to the top left corner, verify pip window is
  // repositioned to avoid overlap with camera preview.
  const gfx::Vector2d delta(-20, -20);
  DragPreviewToPoint(preview_widget, capture_bounds_center_point + delta);
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kTopLeft);
  EXPECT_NE(origin_pip_window_bounds, pip_window->GetBoundsInScreen());
  EXPECT_FALSE(preview_widget->GetWindowBoundsInScreen().Intersects(
      pip_window->GetBoundsInScreen()));
}

TEST_P(CaptureModeCameraPreviewTest,
       CameraPreviewDeintersectsWithAutoclickBar) {
  // Update display size big enough to make sure when capture source is
  // `kWindow`, the selected window is not system tray.
  UpdateDisplay("1366x768");

  views::Widget* autoclick_bubble_widget = EnableAndGetAutoClickBubbleWidget();
  EXPECT_TRUE(autoclick_bubble_widget->IsVisible());
  const gfx::Rect origin_autoclick_bar_bounds =
      autoclick_bubble_widget->GetWindowBoundsInScreen();

  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();

  // Verify that after preview widget is enabled, autoclick bar is repositioned
  // to avoid the overlap with camera preview.
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
  const gfx::Rect current_autoclick_bar_bounds =
      autoclick_bubble_widget->GetWindowBoundsInScreen();
  EXPECT_NE(origin_autoclick_bar_bounds, current_autoclick_bar_bounds);
  EXPECT_FALSE(current_autoclick_bar_bounds.Intersects(
      preview_widget->GetWindowBoundsInScreen()));
}

TEST_P(CaptureModeCameraPreviewTest,
       CameraPreviewDeintersectsWithSystemTrayOnSizeChanged) {
  // Update display size to make sure when system tray is open, camera preview
  // can stay in the same side with it when camera preview is collapsed,
  // otherwise, camera preview should be snapped to the other side of the
  // display.
  UpdateDisplay("1366x950");

  StartCaptureSessionWithParam();
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* preview_widget = camera_controller->camera_preview_widget();

  // Verify camera preview is enabled, and by default, the current snap position
  // should be `kBottomRight`.
  EXPECT_TRUE(preview_widget);
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);

  // Click on resize button to collapse camera preview.
  auto* resize_button = GetPreviewResizeButton();
  auto* event_generator = GetEventGenerator();
  ClickOnView(resize_button, event_generator);
  EXPECT_TRUE(camera_controller->is_camera_preview_collapsed());

  StartVideoRecordingImmediately();
  // Open system tray.
  auto* system_tray = GetPrimaryUnifiedSystemTray();
  event_generator->MoveMouseTo(system_tray->GetBoundsInScreen().CenterPoint());
  event_generator->ClickLeftButton();
  EXPECT_TRUE(system_tray->IsBubbleShown());

  // After system tray is shown, verify that camera preview is snapped to the
  // top right corner, and there's no overlap between camera preview and system
  // tray.
  EXPECT_TRUE(system_tray->IsBubbleShown());
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kTopRight);
  EXPECT_FALSE(preview_widget->GetWindowBoundsInScreen().Intersects(
      system_tray->GetBoundsInScreen()));

  // Click on the resize button to enlarge camera preview. Verify that camera
  // preview remains snapped to the top right corner, since there's no overlap.
  ClickOnView(resize_button, event_generator);
  EXPECT_FALSE(preview_widget->GetWindowBoundsInScreen().Intersects(
      system_tray->GetBoundsInScreen()));
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kTopRight);
}

TEST_P(CaptureModeCameraPreviewTest, CameraPreviewSpecs) {
  AddDefaultCamera();
  CaptureModeTestApi().SelectCameraAtIndex(0);
  auto* camera_controller = GetCameraController();

  struct {
    CameraPreviewState preview_state;
    std::string scope_trace;
  } kTestCases[] = {
      {CameraPreviewState::kCollapsible, "Collapsible Preview"},
      {CameraPreviewState::kNotCollapsible, "Not Collapsible Preview"},
      {CameraPreviewState::kHidden, "Hidden Preview"},
  };

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.scope_trace);

    UpdateDisplay("1366x700");
    StartCaptureSessionWithParam();
    auto* camera_preview_widget = camera_controller->camera_preview_widget();
    auto* camera_preview_view = camera_controller->camera_preview_view();
    EXPECT_TRUE(camera_preview_widget);
    EXPECT_TRUE(camera_preview_widget->IsVisible());
    EXPECT_TRUE(camera_preview_view->is_collapsible());

    ResizeSurfaceSoCameraPreviewBecomes(test_case.preview_state);
    const auto preview_screen_bounds =
        camera_preview_widget->GetWindowBoundsInScreen();
    switch (test_case.preview_state) {
      case CameraPreviewState::kCollapsible:
        EXPECT_TRUE(camera_preview_widget->IsVisible());
        EXPECT_TRUE(camera_preview_view->is_collapsible());
        EXPECT_EQ(preview_screen_bounds.size(),
                  GetExpectedPreviewSize(/*collapsed=*/false));
        break;

      case CameraPreviewState::kNotCollapsible:
        EXPECT_TRUE(camera_preview_widget->IsVisible());
        EXPECT_FALSE(camera_preview_view->is_collapsible());
        EXPECT_EQ(preview_screen_bounds.size(),
                  GetExpectedPreviewSize(/*collapsed=*/false));
        break;

      case CameraPreviewState::kHidden:
        EXPECT_FALSE(camera_preview_widget->IsVisible());
        EXPECT_FALSE(camera_preview_view->is_collapsible());
        break;
    }
  }
}

// Tests that the resize button will stay visible after mouse exiting the
// preview and time exceeding the predefined duration on mouse event when switch
// access is enabled. And the resize button will behave in a default way if
// switch access is not enabled.
TEST_P(CaptureModeCameraPreviewTest,
       ResizeButtonSwitchAccessVisibilityTestOnMouseEvent) {
  UpdateDisplay("1366x768");

  CaptureModeCameraController* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* event_generator = GetEventGenerator();

  for (const bool switch_access_enabled : {false, true}) {
    AccessibilityController* a11y_controller =
        Shell::Get()->accessibility_controller();
    a11y_controller->switch_access().SetEnabled(switch_access_enabled);
    EXPECT_EQ(switch_access_enabled, a11y_controller->IsSwitchAccessRunning());

    StartCaptureSessionWithParam();
    views::Widget* preview_widget = camera_controller->camera_preview_widget();
    DCHECK(preview_widget);
    gfx::Rect preview_bounds = preview_widget->GetWindowBoundsInScreen();
    CameraPreviewResizeButton* resize_button = GetPreviewResizeButton();

    // Tests the default visibility of the resize button based on whether switch
    // access is enabled or not.
    EXPECT_EQ(resize_button->GetVisible(),
              switch_access_enabled ? true : false);

    event_generator->MoveMouseTo(preview_bounds.CenterPoint());
    EXPECT_TRUE(resize_button->GetVisible());

    auto outside_point = preview_bounds.origin();
    outside_point.Offset(-1, -1);
    event_generator->MoveMouseTo(outside_point);
    base::OneShotTimer* timer = camera_controller->camera_preview_view()
                                    ->resize_button_hide_timer_for_test();
    timer->FireNow();
    EXPECT_EQ(resize_button->GetVisible(),
              switch_access_enabled ? true : false);

    // Tests that the resize button will be hidden when start dragging the
    // camera preview regardless of whether the switch access is enabled or not.
    event_generator->MoveMouseTo(preview_bounds.CenterPoint());
    EXPECT_TRUE(resize_button->GetVisible());
    event_generator->PressLeftButton();
    EXPECT_FALSE(resize_button->GetVisible());
    event_generator->MoveMouseBy(-100, -100);
    EXPECT_FALSE(resize_button->GetVisible());

    // Tests that the resize button will be visible if the switch access is
    // enabled after releasing the drag and not visible otherwise.
    event_generator->ReleaseLeftButton();
    EXPECT_EQ(resize_button->GetVisible(),
              switch_access_enabled ? true : false);

    CaptureModeController::Get()->Stop();
  }
}

// Tests that the resize button will stay visible after tapping on the preview
// and time exceeding the predefined duration on tap event when switch access is
// enabled. And the resize button will behave in a default way if switch
// access is not enabled.
TEST_P(CaptureModeCameraPreviewTest,
       ResizeButtonSwitchAccessVisibilityTestOnTapEvent) {
  UpdateDisplay("1366x768");

  SwitchToTabletMode();
  EXPECT_TRUE(Shell::Get()->IsInTabletMode());

  CaptureModeCameraController* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* event_generator = GetEventGenerator();

  for (const bool switch_access_enabled : {false, true}) {
    AccessibilityController* a11y_controller =
        Shell::Get()->accessibility_controller();
    a11y_controller->switch_access().SetEnabled(switch_access_enabled);
    EXPECT_EQ(switch_access_enabled, a11y_controller->IsSwitchAccessRunning());

    StartCaptureSessionWithParam();
    views::Widget* preview_widget = camera_controller->camera_preview_widget();
    DCHECK(preview_widget);
    gfx::Rect preview_bounds = preview_widget->GetWindowBoundsInScreen();
    CameraPreviewResizeButton* resize_button = GetPreviewResizeButton();

    // Tests the default visibility of the resize button based on whether switch
    // access is enabled or not.
    EXPECT_EQ(resize_button->GetVisible(),
              switch_access_enabled ? true : false);

    event_generator->GestureTapAt(preview_bounds.CenterPoint());
    EXPECT_TRUE(resize_button->GetVisible());

    base::OneShotTimer* timer = camera_controller->camera_preview_view()
                                    ->resize_button_hide_timer_for_test();
    if (timer->IsRunning())
      timer->FireNow();

    EXPECT_EQ(resize_button->GetVisible(),
              switch_access_enabled ? true : false);
    CaptureModeController::Get()->Stop();
  }
}

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

// -----------------------------------------------------------------------------
// CameraPreviewWithNotificationTest:

class CameraPreviewWithNotificationTest : public CaptureModeCameraTest {
 public:
  CameraPreviewWithNotificationTest() = default;
  CameraPreviewWithNotificationTest(const CameraPreviewWithNotificationTest&) =
      delete;
  CameraPreviewWithNotificationTest& operator=(
      const CameraPreviewWithNotificationTest&) = delete;
  ~CameraPreviewWithNotificationTest() override = default;

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

    auto test_api = std::make_unique<NotificationCenterTestApi>();
    // Add a notification to show the notification center tray in the shelf.
    test_api->AddNotification();
    ASSERT_TRUE(test_api->IsTrayShown());
  }
};

TEST_F(CameraPreviewWithNotificationTest,
       AvoidCollisionWithNotificationBubbleShownFirst) {
  NotificationCenterTray* notification_center_tray =
      GetPrimaryNotificationCenterTray();

  // Click the notification center tray to open the corresponding bubble.
  LeftClickOn(notification_center_tray);
  EXPECT_TRUE(notification_center_tray->IsBubbleShown());

  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  // Verify current default snap position is `kBottomRight` before we select a
  // camera device.
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);

  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  EXPECT_TRUE(notification_center_tray->IsBubbleShown());

  auto* preview_widget = camera_controller->camera_preview_widget();
  // The camera preview should not intersect with the notification bubble when
  // it is shown. The snap position should be updated to avoid this.
  EXPECT_FALSE(
      notification_center_tray->GetBubbleView()->GetBoundsInScreen().Intersects(
          preview_widget->GetWindowBoundsInScreen()));
  EXPECT_NE(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
}

TEST_F(CameraPreviewWithNotificationTest,
       AvoidCollisionWithCameraPreviewShownFirst) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  StartVideoRecordingImmediately();

  NotificationCenterTray* notification_center_tray =
      GetPrimaryNotificationCenterTray();

  // The camera preview should be snapped to the bottom right when the
  // notification bubble is not shown.
  EXPECT_FALSE(notification_center_tray->IsBubbleShown());
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);

  // Click the notification center tray to open the corresponding bubble. The
  // camera preview snap position should be updated to avoid the collision.
  LeftClickOn(notification_center_tray);
  EXPECT_TRUE(notification_center_tray->IsBubbleShown());
  auto* preview_widget = camera_controller->camera_preview_widget();
  EXPECT_FALSE(
      notification_center_tray->GetBubbleView()->GetBoundsInScreen().Intersects(
          preview_widget->GetWindowBoundsInScreen()));
  EXPECT_NE(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
}

// -----------------------------------------------------------------------------
// CameraPreviewWithHoldingSpaceTest:

class CameraPreviewWithHoldingSpaceTest : public CaptureModeCameraTest {
 public:
  CameraPreviewWithHoldingSpaceTest() = default;
  CameraPreviewWithHoldingSpaceTest(const CameraPreviewWithHoldingSpaceTest&) =
      delete;
  CameraPreviewWithHoldingSpaceTest& operator=(
      const CameraPreviewWithHoldingSpaceTest&) = delete;
  ~CameraPreviewWithHoldingSpaceTest() override = default;

  HoldingSpaceModel* model() { return &model_; }

  testing::NiceMock<MockHoldingSpaceClient>* client() {
    return &holding_space_client_;
  }

  HoldingSpaceTestApi* holding_space_test_api() {
    return holding_space_test_api_.get();
  }

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

    holding_space_test_api_ = std::make_unique<HoldingSpaceTestApi>();
    AccountId user_account = AccountId::FromUserEmail(kTestUser);
    HoldingSpaceController::Get()->RegisterClientAndModelForUser(
        user_account, client(), model());

    TestSessionControllerClient* session = GetSessionControllerClient();
    session->AddUserSession(kTestUser);
    holding_space_prefs::MarkTimeOfFirstAvailability(
        session->GetUserPrefService(user_account));
    holding_space_prefs::MarkTimeOfFirstAdd(
        session->GetUserPrefService(user_account));
    session->SwitchActiveUser(user_account);
  }

  void TearDown() override {
    holding_space_test_api_.reset();
    CaptureModeCameraTest::TearDown();
  }

 private:
  std::unique_ptr<HoldingSpaceTestApi> holding_space_test_api_;
  testing::NiceMock<MockHoldingSpaceClient> holding_space_client_;
  HoldingSpaceModel model_;
};

TEST_F(CameraPreviewWithHoldingSpaceTest,
       AvoidCollisionWithHoldingSpaceBubbleShownFirst) {
  EXPECT_TRUE(holding_space_test_api()->IsShowingInShelf());
  // Tap on the holding space tray to show the corresponding bubble.
  holding_space_test_api()->Show();
  EXPECT_TRUE(holding_space_test_api()->IsShowing());

  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  // Verify current default snap position is `kBottomRight` before we select a
  // camera device.
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);

  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  EXPECT_TRUE(holding_space_test_api()->IsShowing());

  auto* preview_widget = camera_controller->camera_preview_widget();
  // The camera preview should not intersect with the holding space bubble when
  // it is shown. The snap position should be updated to avoid this.
  EXPECT_FALSE(
      holding_space_test_api()->GetBubble()->GetBoundsInScreen().Intersects(
          preview_widget->GetWindowBoundsInScreen()));
  EXPECT_NE(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
}

TEST_F(CameraPreviewWithHoldingSpaceTest,
       AvoidCollisionWithCameraPreviewShownFirst) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  AddDefaultCamera();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  StartVideoRecordingImmediately();

  EXPECT_TRUE(holding_space_test_api()->IsShowingInShelf());
  // The camera preview should be snapped to the bottom right when the holding
  // space bubble is not shown.
  EXPECT_FALSE(holding_space_test_api()->IsShowing());
  EXPECT_EQ(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);

  // Tap on the holding space tray to show the corresponding bubble. The camera
  // preview snap position should be updated to avoid the collision.
  holding_space_test_api()->Show();
  EXPECT_TRUE(holding_space_test_api()->IsShowing());
  auto* preview_widget = camera_controller->camera_preview_widget();
  EXPECT_FALSE(
      holding_space_test_api()->GetBubble()->GetBoundsInScreen().Intersects(
          preview_widget->GetWindowBoundsInScreen()));
  EXPECT_NE(camera_controller->camera_preview_snap_position(),
            CameraPreviewSnapPosition::kBottomRight);
}

// -----------------------------------------------------------------------------
// ProjectorCaptureModeCameraTest:

class ProjectorCaptureModeCameraTest : public CaptureModeCameraTest {
 public:
  ProjectorCaptureModeCameraTest() = default;
  ~ProjectorCaptureModeCameraTest() override = default;

  // CaptureModeCameraTest:
  void SetUp() override {
    CaptureModeCameraTest::SetUp();
    projector_helper_.SetUp();
  }

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

 private:
  ProjectorCaptureModeIntegrationHelper projector_helper_;
};

TEST_F(ProjectorCaptureModeCameraTest, NoAvailableCameras) {
  // Initially no camera should be selected.
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());

  // Starting a projector session should not result in showing any cameras, or
  // any crashes.
  StartProjectorModeSession();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  EXPECT_FALSE(camera_controller->camera_preview_widget());
}

TEST_F(ProjectorCaptureModeCameraTest, FirstCamSelectedByDefault) {
  AddDefaultCamera();

  // Initially no camera should be selected.
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());

  // Starting a projector session should result in selecting the first available
  // camera by default, and its preview should be visible.
  StartProjectorModeSession();
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  EXPECT_TRUE(camera_controller->camera_preview_widget());
}

// Regression test for http://b/353883311. Tests that starting a default capture
// mode session and dismissing it during an active Projector recording should
// not revert the automatically selected camera for the on-going recording.
TEST_F(ProjectorCaptureModeCameraTest,
       DefaultCaptureSessionWhileProjectorRecording) {
  AddDefaultCamera();

  // Start a Projector-initiated session and start recording. The first
  // available camera will be selected by default.
  StartProjectorModeSession();
  auto* camera_controller = GetCameraController();
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  CaptureModeTestApi test_api;
  test_api.PerformCapture();
  WaitForRecordingToStart();
  auto* controller = CaptureModeController::Get();
  EXPECT_TRUE(controller->is_recording_in_progress());
  EXPECT_TRUE(camera_controller->camera_preview_widget());

  // Start a new default screenshot session while the projector recording is in
  // progress. Ending this session should not revert the auto-selected camera.
  test_api.StartForFullscreen(/*for_video=*/false);
  controller->Stop();
  EXPECT_TRUE(controller->is_recording_in_progress());
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  EXPECT_TRUE(camera_controller->camera_preview_widget());
}

TEST_F(ProjectorCaptureModeCameraTest,
       SessionStartsWithAnAlreadySelectedCamera) {
  const std::string model_id_1 = "model1";
  const std::string model_id_2 = "model2";
  AddFakeCamera("/dev/video0", "fake cam 1", model_id_1);
  AddFakeCamera("/dev/video1", "fake cam 2", model_id_2);

  // Initially there's a camera already selected before we start the session,
  // and it's the second camera in the list.
  auto* camera_controller = GetCameraController();
  CameraId cam_id_1(model_id_1, 1);
  CameraId cam_id_2(model_id_2, 1);
  EXPECT_EQ(cam_id_1, camera_controller->available_cameras()[0].camera_id);
  EXPECT_EQ(cam_id_2, camera_controller->available_cameras()[1].camera_id);
  camera_controller->SetSelectedCamera(cam_id_2);
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());

  // Starting a projector session should not result in selecting the first
  // camera. The already selected camera should remain as is.
  StartProjectorModeSession();
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  EXPECT_EQ(cam_id_2, camera_controller->selected_camera());
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  CaptureModeController::Get()->Stop();

  // Starting a normal screen capture session and the previously selected
  // `cam_id_2` should remain being selected.
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  EXPECT_EQ(cam_id_2, camera_controller->selected_camera());
  EXPECT_TRUE(camera_controller->camera_preview_widget());
}

// Tests that the recording starts with camera metrics are recorded correctly in
// a projector-initiated recording.
TEST_F(ProjectorCaptureModeCameraTest,
       ProjectorRecordingStartsWithCameraHistogramTest) {
  base::HistogramTester histogram_tester;
  constexpr char kHistogramNameBase[] = "RecordingStartsWithCamera";

  AddDefaultCamera();

  struct {
    bool tablet_enabled;
    bool camera_on;
  } kTestCases[] = {
      {false, false},
      {false, true},
      {true, false},
      {true, true},
  };

  for (const auto test_case : kTestCases) {
    if (test_case.tablet_enabled) {
      SwitchToTabletMode();
      EXPECT_TRUE(Shell::Get()->IsInTabletMode());
    } else {
      EXPECT_FALSE(Shell::Get()->IsInTabletMode());
    }

    const std::string histogram_name = BuildHistogramName(
        kHistogramNameBase,
        CaptureModeTestApi().GetBehavior(BehaviorType::kProjector),
        /*append_ui_mode_suffix=*/true);
    histogram_tester.ExpectBucketCount(histogram_name, test_case.camera_on, 0);

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

    StartProjectorModeSession();
    EXPECT_TRUE(controller->IsActive());
    auto* session = controller->capture_mode_session();
    ASSERT_TRUE(session);

    GetCameraController()->SetSelectedCamera(
        test_case.camera_on ? CameraId(kDefaultCameraModelId, 1) : CameraId());

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

    WaitForSeconds(1);

    controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
    WaitForCaptureFileToBeSaved();

    histogram_tester.ExpectBucketCount(histogram_name, test_case.camera_on, 1);
  }
}

// Tests that the auto-selected camera in the projector-initiated capture mode
// session will not be carried over to the normal capture mode session before
// the video recording starts.
TEST_F(ProjectorCaptureModeCameraTest,
       DoNotRememberProjectorCameraSelectionBeforeVideoRecording) {
  AddDefaultCamera();

  // Initially no camera should be selected for the normal capture mode session.
  auto* controller = CaptureModeController::Get();
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  controller->Stop();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());

  // Starts a projector-initiated capture mode session, the camera will be
  // auto-selected and reset to previous settings after the session.
  StartProjectorModeSession();
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  controller->Stop();

  // Starts the capture mode session again and the camera selection settings
  // will be restored.
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
}

// Tests that the auto-selected camera in the projector-initiated capture mode
// session will not be carried over to the normal capture mode session after
// completing a video recording.
TEST_F(ProjectorCaptureModeCameraTest,
       DoNotRememberProjectorCameraSelectionAfterVideoRecording) {
  AddDefaultCamera();

  auto* controller = CaptureModeController::Get();
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
  controller->Stop();

  // Starts a projector-initiated capture mode session and begin video
  // recording, the camera will be auto-selected and reset to previous settings
  // after the session.
  StartProjectorModeSession();
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  StartVideoRecordingImmediately();
  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
  WaitForCaptureFileToBeSaved();

  // Starts the capture mode session again and the camera selection settings
  // will be restored.
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  EXPECT_FALSE(camera_controller->selected_camera().is_valid());
}

// A test fixture for testing the rendered video frames. The boolean parameter
// determines the type of the buffer that backs the video frames. `true` means
// the `kGpuMemoryBuffer` is used, `false` means the `kSharedMemory` buffer type
// is used.
class CaptureModeCameraFramesTest : public CaptureModeCameraTest,
                                    public testing::WithParamInterface<bool> {
 public:
  CaptureModeCameraFramesTest() {
    // Ensure pixels get drawn since they are verified by tests.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        ::switches::kEnablePixelOutputInTests);
  }

  CaptureModeCameraFramesTest(const CaptureModeCameraFramesTest&) = delete;
  CaptureModeCameraFramesTest& operator=(const CaptureModeCameraFramesTest&) =
      delete;
  ~CaptureModeCameraFramesTest() override = default;

  bool ShouldUseGpuMemoryBuffers() const { return GetParam(); }

  // CaptureModeCameraFramesTest:
  void SetUp() override {
    CaptureModeCameraTest::SetUp();
    CaptureModeTestApi test_api;
    test_api.SetForceUseGpuMemoryBufferForCameraFrames(
        ShouldUseGpuMemoryBuffers());
    AddDefaultCamera();
    ASSERT_EQ(1u, test_api.GetNumberOfAvailableCameras());
    test_api.SelectCameraAtIndex(0);
    const CameraId camera_id(kDefaultCameraModelId, 1);
    EXPECT_EQ(camera_id, GetCameraController()->selected_camera());
  }

  void TearDown() override {
    CaptureModeTestApi().SetForceUseGpuMemoryBufferForCameraFrames(false);
    CaptureModeCameraTest::TearDown();
  }
};

namespace {

// Waits for several rendered frames and verifies that the content of the
// received video frames are the same as that of the produced video frames.
void WaitForAndVerifyRenderedVideoFrame() {
  // PaintCanvasVideoRenderer needs a context provider that is capable of GPU
  // raster to copy the video frame to a bitmap.
  auto context_provider =
      base::MakeRefCounted<viz::TestInProcessContextProvider>(
          viz::TestContextType::kGpuRaster, /*support_locking=*/false);
  auto result = context_provider->BindToCurrentSequence();
  CHECK_EQ(result, gpu::ContextResult::kSuccess);

  // Render a number of frames that are 3 times the size of the buffer pool.
  // This allows us to exercise calls to `OnNewBuffer()` and potentially
  // `OnFrameDropped()`.
  for (size_t i = 0; i < 3 * FakeCameraDevice::kMaxBufferCount; ++i) {
    base::RunLoop loop;
    CaptureModeTestApi().SetOnCameraVideoFrameRendered(
        base::BindLambdaForTesting([&loop, &context_provider](
                                       scoped_refptr<media::VideoFrame> frame) {
          ASSERT_TRUE(frame);
          const gfx::Size frame_size = frame->visible_rect().size();
          const auto produced_frame_bitmap =
              FakeCameraDevice::GetProducedFrameAsBitmap(frame_size);

          media::PaintCanvasVideoRenderer renderer;
          SkBitmap received_frame_bitmap;
          received_frame_bitmap.allocN32Pixels(frame_size.width(),
                                               frame_size.height());
          cc::SkiaPaintCanvas canvas(received_frame_bitmap);
          renderer.Copy(frame, &canvas, context_provider.get());

          EXPECT_TRUE(gfx::test::AreBitmapsEqual(produced_frame_bitmap,
                                                 received_frame_bitmap));

          loop.Quit();
        }));
    loop.Run();
  }
}

}  // namespace

TEST_P(CaptureModeCameraFramesTest, VerifyFrames) {
  CaptureModeTestApi().StartForFullscreen(/*for_video=*/true);
  EXPECT_TRUE(GetCameraController()->camera_preview_widget());
  WaitForAndVerifyRenderedVideoFrame();
}

TEST_P(CaptureModeCameraFramesTest, TurnOffCameraWhileRendering) {
  CaptureModeTestApi test_api;
  test_api.StartForFullscreen(/*for_video=*/true);
  auto* camera_controller = GetCameraController();
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  WaitForAndVerifyRenderedVideoFrame();
  test_api.TurnCameraOff();
  EXPECT_FALSE(camera_controller->camera_preview_widget());
}

TEST_P(CaptureModeCameraFramesTest, DisconnectCameraWhileRendering) {
  CaptureModeTestApi test_api;
  test_api.StartForFullscreen(/*for_video=*/true);
  auto* camera_controller = GetCameraController();
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  WaitForAndVerifyRenderedVideoFrame();
  RemoveDefaultCamera();
  EXPECT_FALSE(camera_controller->camera_preview_widget());
}

TEST_P(CaptureModeCameraFramesTest, SelectAnotherCameraWhileRendering) {
  CaptureModeTestApi test_api;
  test_api.StartForFullscreen(/*for_video=*/true);
  auto* camera_controller = GetCameraController();
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  auto* preview_view = camera_controller->camera_preview_view();
  ASSERT_TRUE(preview_view);
  EXPECT_EQ(preview_view->camera_id(), camera_controller->selected_camera());
  WaitForAndVerifyRenderedVideoFrame();

  // Adding a new camera while rendering an existing one should not affect
  // anything since the new one is not selected yet.
  const std::string device_id = "/dev/video0";
  const std::string display_name = "Integrated Webcam";
  const std::string model_id = "0123:4567";
  AddFakeCamera(device_id, display_name, model_id);
  EXPECT_EQ(preview_view, camera_controller->camera_preview_view());

  // Now select the new camera, a new widget should be created immediately for
  // the new camera.
  const CameraId second_camera_id(model_id, 1);
  camera_controller->SetSelectedCamera(second_camera_id);
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  EXPECT_NE(preview_view, camera_controller->camera_preview_view());
  preview_view = camera_controller->camera_preview_view();
  EXPECT_EQ(preview_view->camera_id(), second_camera_id);
  WaitForAndVerifyRenderedVideoFrame();
}

// Regression test for https://crbug.com/1316230.
TEST_P(CaptureModeCameraFramesTest, CameraFatalErrors) {
  CaptureModeTestApi().StartForFullscreen(/*for_video=*/true);
  auto* camera_controller = GetCameraController();
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  WaitForAndVerifyRenderedVideoFrame();

  // When a camera fatal error happens during rendering, we detect that an
  // consider it as a camera disconnection, which will result in the temporary
  // removal of the preview, before it gets re-added again when we refresh the
  // list of cameras.
  auto* video_source_provider = GetTestDelegate()->video_source_provider();
  video_source_provider->TriggerFatalErrorOnCamera(kDefaultCameraDeviceId);
  CameraDevicesChangeWaiter().Wait();
  EXPECT_FALSE(camera_controller->camera_preview_widget());
  EXPECT_TRUE(camera_controller->selected_camera().is_valid());

  // Now wait for the camera to be re-added again.
  CameraDevicesChangeWaiter().Wait();
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  WaitForAndVerifyRenderedVideoFrame();
}

INSTANTIATE_TEST_SUITE_P(All, CaptureModeCameraFramesTest, testing::Bool());

// The test fixture for starting test without active session.
using NoSessionCaptureModeCameraTest = NoSessionAshTestBase;

// Tests that camera info is requested after the user logs in instead of on
// Chrome startup.
TEST_F(NoSessionCaptureModeCameraTest, RequestCameraInfoAfterUserLogsIn) {
  auto* camera_controller = GetCameraController();
  GetTestDelegate()->video_source_provider()->AddFakeCameraWithoutNotifying(
      "/dev/video0", "Integrated Webcam", "0123:4567",
      media::MEDIA_VIDEO_FACING_NONE);

  // Verify that the camera devices info is not updated yet.
  EXPECT_TRUE(camera_controller->available_cameras().empty());

  // Simulate the user login process and wait for the camera info to be updated.
  {
    base::RunLoop loop;
    camera_controller->SetOnCameraListReceivedForTesting(loop.QuitClosure());
    SimulateUserLogin("[email protected]", user_manager::UserType::kRegular);
    loop.Run();
  }

  // Verify that after the user logs in, the camera info is up-to-date.
  EXPECT_EQ(camera_controller->available_cameras().size(), 1u);
}

TEST_F(CaptureModeCameraTest, CameraPrivacyIndicators) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);

  auto* message_center = message_center::MessageCenter::Get();
  auto capture_mode_privacy_notification_id =
      GetPrivacyIndicatorsNotificationId(kCaptureModePrivacyIndicatorId);

  // Initially the session doesn't show any camera preview since the camera
  // hasn't connected yet. There should be no privacy indicators.
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  EXPECT_FALSE(camera_controller->camera_preview_widget());
  EXPECT_FALSE(IsCameraIndicatorIconVisible());
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
  EXPECT_FALSE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));

  // Once the camera gets connected, the camera privacy indicator
  // icon/notification should show. No microphone yet (not until recording
  // starts with audio).
  AddDefaultCamera();
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  EXPECT_TRUE(IsCameraIndicatorIconVisible());
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
  EXPECT_TRUE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));

  // If the camera gets disconnected for some reason, the indicator should go
  // away, and come back once it reconnects again.
  RemoveDefaultCamera();
  EXPECT_FALSE(camera_controller->camera_preview_widget());
  // The widget closes its window asynchronously, run a loop to finish that.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(IsCameraIndicatorIconVisible());
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
  EXPECT_FALSE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));

  AddDefaultCamera();
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  EXPECT_TRUE(IsCameraIndicatorIconVisible());
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
  EXPECT_TRUE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));
}

TEST_F(CaptureModeCameraTest, DuringRecordingPrivacyIndicators) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);

  auto* message_center = message_center::MessageCenter::Get();
  auto capture_mode_privacy_notification_id =
      GetPrivacyIndicatorsNotificationId(kCaptureModePrivacyIndicatorId);

  // Even with the selected camera present, no indicators will show until the
  // capture session starts.
  auto* camera_controller = GetCameraController();
  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  AddDefaultCamera();
  EXPECT_FALSE(camera_controller->camera_preview_widget());
  EXPECT_FALSE(IsCameraIndicatorIconVisible());
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
  EXPECT_FALSE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));

  auto* capture_controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                                 CaptureModeType::kVideo);
  EXPECT_TRUE(camera_controller->camera_preview_widget());
  EXPECT_TRUE(IsCameraIndicatorIconVisible());
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
  EXPECT_TRUE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));

  // When the user selects audio recording, the idicators won't change.
  // Recording has to start first.
  capture_controller->SetAudioRecordingMode(AudioRecordingMode::kMicrophone);
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());

  StartRecordingFromSource(CaptureModeSource::kFullscreen);
  EXPECT_TRUE(IsMicrophoneIndicatorIconVisible());
  EXPECT_TRUE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));

  // Once recording ends, both indicators should disappear.
  capture_controller->EndVideoRecording(
      EndRecordingReason::kStopRecordingButton);
  WaitForCaptureFileToBeSaved();
  EXPECT_FALSE(IsCameraIndicatorIconVisible());
  EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
  EXPECT_FALSE(message_center->FindNotificationById(
      capture_mode_privacy_notification_id));
}

TEST_F(CaptureModeCameraTest, CameraPreviewViewAccessibleProperties) {
  StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
  AddDefaultCamera();
  GetCameraController()->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
  auto* camera_preview_view = GetCameraController()->camera_preview_view();

  ui::AXNodeData data;
  camera_preview_view->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.role, ax::mojom::Role::kVideo);
  EXPECT_EQ(
      data.GetString16Attribute(ax::mojom::StringAttribute::kName),
      l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED));
}

TEST_F(CaptureModeCameraTest, CaptureModeMenuHeaderAccessibleProperties) {
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  OpenSettingsView();
  CaptureModeSettingsTestApi test_api;
  AddDefaultCamera();
  auto* menu_header = test_api.GetCameraMenuHeader();
  ui::AXNodeData data;

  menu_header->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.role, ax::mojom::Role::kHeader);
}

}  // namespace ash