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

#include "content/browser/media/capture/frame_sink_video_capture_device.h"

#include <array>
#include <memory>

#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/shared_memory_mapping.h"
#include "components/viz/common/surfaces/region_capture_bounds.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "media/base/video_frame.h"
#include "media/capture/video/video_frame_receiver.h"
#include "media/capture/video_capture_types.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"

using testing::_;
using testing::ByRef;
using testing::Eq;
using testing::Expectation;
using testing::Ge;
using testing::NiceMock;
using testing::NotNull;
using testing::SaveArg;
using testing::Sequence;
using testing::StrNe;

namespace content {
namespace {

// Threading notes: Throughout these tests, the UI thread (the main test
// thread) represents the executor of all external-to-device operations. This
// means that it represents everything that runs on the UI thread in the browser
// process, plus anything that would run in the VIZ process. The IO thread is
// used as the "device thread" for content::FrameSinkVideoCaptureDevice.
#define DCHECK_ON_DEVICE_THREAD() DCHECK_CURRENTLY_ON(BrowserThread::IO)
#define DCHECK_NOT_ON_DEVICE_THREAD() DCHECK_CURRENTLY_ON(BrowserThread::UI)

// Convenience macro to block the test procedure and run all pending UI tasks.
#define RUN_UI_TASKS() base::RunLoop().RunUntilIdle()

// Convenience macro to post a task to run on the device thread.
#define POST_DEVICE_TASK(closure) \
  GetIOThreadTaskRunner({})->PostTask(FROM_HERE, closure)

// Convenience macro to block the test procedure until all pending tasks have
// run on the device thread.
#define WAIT_FOR_DEVICE_TASKS()             \
  task_environment_.RunIOThreadUntilIdle(); \
  RUN_UI_TASKS()

// Capture parameters.
constexpr gfx::Size kResolution = gfx::Size(320, 180);
constexpr int kMaxFrameRate = 25;  // It evenly divides 1 million usec.
constexpr base::TimeDelta kMinCapturePeriod =
    base::Microseconds(base::Time::kMicrosecondsPerSecond / kMaxFrameRate);
constexpr media::VideoPixelFormat kFormat = media::PIXEL_FORMAT_I420;

// Video buffer parameters.
constexpr bool kNotPremapped = false;

// Helper to return the capture parameters packaged in a VideoCaptureParams.
media::VideoCaptureParams GetCaptureParams() {
  media::VideoCaptureParams params;
  params.requested_format =
      media::VideoCaptureFormat(kResolution, kMaxFrameRate, kFormat);
  return params;
}

// Mock for the FrameSinkVideoCapturer running in the VIZ process.
class MockFrameSinkVideoCapturer : public viz::mojom::FrameSinkVideoCapturer {
 public:
  MockFrameSinkVideoCapturer() = default;

  bool is_bound() const { return receiver_.is_bound(); }

  void Bind(
      mojo::PendingReceiver<viz::mojom::FrameSinkVideoCapturer> receiver) {
    DCHECK_NOT_ON_DEVICE_THREAD();
    receiver_.Bind(std::move(receiver));
  }

  MOCK_METHOD1(SetFormat, void(media::VideoPixelFormat format));
  MOCK_METHOD1(SetMinCapturePeriod, void(base::TimeDelta min_period));
  MOCK_METHOD1(SetMinSizeChangePeriod, void(base::TimeDelta));
  MOCK_METHOD2(SetAnimationFpsLockIn,
               void(bool enabled, float majority_damaged_pixel_min_ratio));
  MOCK_METHOD3(SetResolutionConstraints,
               void(const gfx::Size& min_size,
                    const gfx::Size& max_size,
                    bool use_fixed_aspect_ratio));
  MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
  void ChangeTarget(const std::optional<viz::VideoCaptureTarget>& target,
                    uint32_t sub_capture_version) final {
    DCHECK_NOT_ON_DEVICE_THREAD();
    MockChangeTarget(target, sub_capture_version);
  }
  MOCK_METHOD2(MockChangeTarget,
               void(const std::optional<viz::VideoCaptureTarget>& target,
                    uint32_t sub_capture_version));
  void Start(
      mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumer> consumer,
      viz::mojom::BufferFormatPreference buffer_format_preference) final {
    DCHECK_NOT_ON_DEVICE_THREAD();
    consumer_.Bind(std::move(consumer));
    MockStart(consumer_.get(), buffer_format_preference);
  }
  MOCK_METHOD2(
      MockStart,
      void(viz::mojom::FrameSinkVideoConsumer* consumer,
           viz::mojom::BufferFormatPreference buffer_format_preference));
  void Stop() final {
    DCHECK_NOT_ON_DEVICE_THREAD();
    consumer_.reset();
    MockStop();
  }
  MOCK_METHOD(void, MockStop, ());
  MOCK_METHOD(void, RequestRefreshFrame, (), (override));
  MOCK_METHOD(void, InvalidateBuffers, (), (override));
  MOCK_METHOD(void,
              CreateOverlay,
              (int32_t stacking_index,
               mojo::PendingReceiver<viz::mojom::FrameSinkVideoCaptureOverlay>
                   receiver),
              (override));

 private:
  mojo::Receiver<viz::mojom::FrameSinkVideoCapturer> receiver_{this};
  mojo::Remote<viz::mojom::FrameSinkVideoConsumer> consumer_;
};

// Represents the FrameSinkVideoConsumerFrameCallbacks instance in the VIZ
// process.
class MockFrameSinkVideoConsumerFrameCallbacks
    : public viz::mojom::FrameSinkVideoConsumerFrameCallbacks {
 public:
  MockFrameSinkVideoConsumerFrameCallbacks() = default;

  void Bind(
      mojo::PendingReceiver<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
          receiver) {
    DCHECK_NOT_ON_DEVICE_THREAD();
    receiver_.Bind(std::move(receiver));
  }

  MOCK_METHOD0(Done, void());
  MOCK_METHOD1(ProvideFeedback, void(const media::VideoCaptureFeedback&));

 private:
  mojo::Receiver<viz::mojom::FrameSinkVideoConsumerFrameCallbacks> receiver_{
      this};
};

// Mock for the VideoFrameReceiver, the point-of-injection of video frames into
// the video capture stack. It's mocked methods are called on the device thread.
// Some methods stash objects of interest, which test code must grab via the
// TakeXYZ() utility methods (called on the main thread).
class MockVideoFrameReceiver : public media::VideoFrameReceiver {
 public:
  using Buffer = media::VideoCaptureDevice::Client::Buffer;

  ~MockVideoFrameReceiver() override {
    DCHECK_ON_DEVICE_THREAD();
    EXPECT_TRUE(buffer_handles_.empty());
    EXPECT_TRUE(feedback_ids_.empty());
    EXPECT_TRUE(access_permissions_.empty());
    EXPECT_TRUE(frame_infos_.empty());
  }

  MOCK_METHOD0(OnCaptureConfigurationChanged, void());
  void OnNewBuffer(int buffer_id,
                   media::mojom::VideoBufferHandlePtr buffer_handle) final {
    DCHECK_ON_DEVICE_THREAD();
    auto* const raw_pointer = buffer_handle.get();
    buffer_handles_[buffer_id] = std::move(buffer_handle);
    MockOnNewBuffer(buffer_id, raw_pointer);
  }
  MOCK_METHOD2(MockOnNewBuffer,
               void(int buffer_id,
                    media::mojom::VideoBufferHandle* buffer_handle));
  void OnFrameReadyInBuffer(media::ReadyFrameInBuffer frame) final {
    DCHECK_ON_DEVICE_THREAD();
    feedback_ids_[frame.buffer_id] = frame.frame_feedback_id;
    auto* const raw_pointer_to_permission = frame.buffer_read_permission.get();
    access_permissions_[frame.buffer_id] =
        std::move(frame.buffer_read_permission);
    auto* const raw_pointer_to_info = frame.frame_info.get();
    frame_infos_[frame.buffer_id] = std::move(frame.frame_info);
    MockOnFrameReadyInBuffer(frame.buffer_id, frame.frame_feedback_id,
                             raw_pointer_to_permission, raw_pointer_to_info);
  }
  MOCK_METHOD4(MockOnFrameReadyInBuffer,
               void(int buffer_id,
                    int frame_feedback_id,
                    Buffer::ScopedAccessPermission* buffer_read_permission,
                    const media::mojom::VideoFrameInfo* frame_info));
  MOCK_METHOD1(OnBufferRetired, void(int buffer_id));
  MOCK_METHOD1(OnError, void(media::VideoCaptureError error));
  MOCK_METHOD1(OnFrameDropped, void(media::VideoCaptureFrameDropReason reason));
  MOCK_METHOD1(OnNewCaptureVersion,
               void(media::CaptureVersion capture_version));
  MOCK_METHOD0(OnFrameWithEmptyRegionCapture, void());
  MOCK_METHOD1(OnLog, void(const std::string& message));
  MOCK_METHOD0(OnStarted, void());
  MOCK_METHOD0(OnStopped, void());
  void OnStartedUsingGpuDecode() final { NOTREACHED(); }

  base::ReadOnlySharedMemoryRegion TakeBufferHandle(int buffer_id) {
    DCHECK_NOT_ON_DEVICE_THREAD();
    const auto it = buffer_handles_.find(buffer_id);
    if (it == buffer_handles_.end()) {
      ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
      return base::ReadOnlySharedMemoryRegion();
    }
    CHECK(it->second->is_read_only_shmem_region());
    auto buffer = std::move(it->second->get_read_only_shmem_region());
    buffer_handles_.erase(it);
    return buffer;
  }

  int TakeFeedbackId(int buffer_id) {
    DCHECK_NOT_ON_DEVICE_THREAD();
    const auto it = feedback_ids_.find(buffer_id);
    if (it == feedback_ids_.end()) {
      ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
      return -1;
    }
    const int feedback_id = it->second;
    feedback_ids_.erase(it);
    return feedback_id;
  }

  void ReleaseAccessPermission(int buffer_id) {
    DCHECK_NOT_ON_DEVICE_THREAD();
    const auto it = access_permissions_.find(buffer_id);
    if (it == access_permissions_.end()) {
      ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
      return;
    }
    access_permissions_.erase(it);
  }

  media::mojom::VideoFrameInfoPtr TakeVideoFrameInfo(int buffer_id) {
    DCHECK_NOT_ON_DEVICE_THREAD();
    const auto it = frame_infos_.find(buffer_id);
    if (it == frame_infos_.end()) {
      ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
      return media::mojom::VideoFrameInfoPtr();
    }
    media::mojom::VideoFrameInfoPtr info = std::move(it->second);
    frame_infos_.erase(it);
    return info;
  }

 private:
  base::flat_map<int, media::mojom::VideoBufferHandlePtr> buffer_handles_;
  base::flat_map<int, int> feedback_ids_;
  base::flat_map<int, std::unique_ptr<Buffer::ScopedAccessPermission>>
      access_permissions_;
  base::flat_map<int, media::mojom::VideoFrameInfoPtr> frame_infos_;
};

// A FrameSinkVideoCaptureDevice, but with CreateCapturer() overridden to bind
// to a MockFrameSinkVideoCapturer instead of the real thing.
class FrameSinkVideoCaptureDeviceForTest : public FrameSinkVideoCaptureDevice {
 public:
  explicit FrameSinkVideoCaptureDeviceForTest(
      MockFrameSinkVideoCapturer* capturer)
      : capturer_(capturer) {}

 protected:
  void CreateCapturer(mojo::PendingReceiver<viz::mojom::FrameSinkVideoCapturer>
                          receiver) final {
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            [](MockFrameSinkVideoCapturer* capturer,
               mojo::PendingReceiver<viz::mojom::FrameSinkVideoCapturer>
                   receiver) { capturer->Bind(std::move(receiver)); },
            capturer_, std::move(receiver)));
  }

  const raw_ptr<MockFrameSinkVideoCapturer> capturer_;
};

// Convenience macros to make a non-blocking FrameSinkVideoCaptureDevice method
// call on the device thread.
#define POST_DEVICE_METHOD_CALL0(method)                                \
  POST_DEVICE_TASK(base::BindOnce(&FrameSinkVideoCaptureDevice::method, \
                                  base::Unretained(device_.get())))
#define POST_DEVICE_METHOD_CALL(method, ...)                            \
  POST_DEVICE_TASK(base::BindOnce(&FrameSinkVideoCaptureDevice::method, \
                                  base::Unretained(device_.get()),      \
                                  __VA_ARGS__))

class FrameSinkVideoCaptureDeviceTest : public testing::Test {
 public:
  FrameSinkVideoCaptureDeviceTest()
      : task_environment_(BrowserTaskEnvironment::REAL_IO_THREAD) {}

  ~FrameSinkVideoCaptureDeviceTest() override { EXPECT_FALSE(device_); }

  void SetUp() override {
    // Create the FrameSinkVideoCaptureDevice on the device thread, and block
    // until complete.
    POST_DEVICE_TASK(base::BindOnce(
        [](FrameSinkVideoCaptureDeviceTest* test) {
          test->device_ = std::make_unique<FrameSinkVideoCaptureDeviceForTest>(
              &test->capturer_);
        },
        this));
    WAIT_FOR_DEVICE_TASKS();
  }

  void TearDown() override {
    // Destroy the FrameSinkVideoCaptureDevice on the device thread, and block
    // until complete.
    POST_DEVICE_TASK(base::BindOnce(
        [](FrameSinkVideoCaptureDeviceTest* test) { test->device_.reset(); },
        this));
    WAIT_FOR_DEVICE_TASKS();
    // Some objects owned by the FrameSinkVideoCaptureDevice may need to be
    // deleted on the UI thread, so run those tasks now.
    RUN_UI_TASKS();
  }

  // Starts-up the FrameSinkVideoCaptureDevice: Sets a frame sink target,
  // creates a capturer, sets the capture parameters; and checks that the mock
  // capturer receives the correct mojo method calls.
  void AllocateAndStartSynchronouslyWithExpectations(
      std::unique_ptr<media::VideoFrameReceiver> receiver) {
    EXPECT_CALL(capturer_, SetFormat(kFormat));
    EXPECT_CALL(capturer_, SetMinCapturePeriod(kMinCapturePeriod));
    EXPECT_CALL(capturer_,
                SetResolutionConstraints(kResolution, kResolution, _));
    const viz::VideoCaptureTarget target(viz::FrameSinkId{1, 1});
    EXPECT_CALL(
        capturer_,
        MockChangeTarget(std::optional<viz::VideoCaptureTarget>(target), 0));
    EXPECT_CALL(
        capturer_,
        MockStart(
            NotNull(),
            viz::mojom::BufferFormatPreference::kPreferMappableSharedImage));

    EXPECT_FALSE(capturer_.is_bound());
    POST_DEVICE_METHOD_CALL(OnTargetChanged, target,
                            /*sub_capture_version=*/0);
    POST_DEVICE_METHOD_CALL(AllocateAndStartWithReceiver, GetCaptureParams(),
                            std::move(receiver));
    WAIT_FOR_DEVICE_TASKS();
    RUN_UI_TASKS();  // Run the task to create the capturer.
    EXPECT_TRUE(capturer_.is_bound());
    WAIT_FOR_DEVICE_TASKS();  // Run the task where the interface is bound, etc.
  }

  // Stops the FrameSinkVideoCaptureDevice and optionally checks that the mock
  // capturer received the Stop() call.
  void StopAndDeAllocateSynchronouslyWithExpectations(
      bool capturer_stopped_also) {
    EXPECT_CALL(capturer_, MockStop()).Times(capturer_stopped_also ? 1 : 0);
    POST_DEVICE_METHOD_CALL0(StopAndDeAllocate);
    WAIT_FOR_DEVICE_TASKS();
  }

  // Simulates what the VIZ capturer would do: Allocates a shared memory buffer,
  // populates it with video content, and calls OnFrameCaptured().
  void SimulateFrameCapture(
      int frame_number,
      MockFrameSinkVideoConsumerFrameCallbacks* callbacks) {
    // Allocate a buffer and fill it with values based on |frame_number|.
    base::MappedReadOnlyRegion region =
        base::ReadOnlySharedMemoryRegion::Create(
            media::VideoFrame::AllocationSize(kFormat, kResolution));
    CHECK(region.IsValid());
    UNSAFE_TODO(memset(region.mapping.memory(), GetFrameFillValue(frame_number),
                       region.mapping.size()));

    mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
        callbacks_remote;
    callbacks->Bind(callbacks_remote.InitWithNewPipeAndPassReceiver());
    // |callbacks_remote| is bound on the main thread, so it needs to be
    // re-bound to the device thread before calling OnFrameCaptured().
    POST_DEVICE_TASK(base::BindOnce(
        [](FrameSinkVideoCaptureDevice* device,
           base::ReadOnlySharedMemoryRegion data, int frame_number,
           mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
               callbacks_remote) {
          device->OnFrameCaptured(
              media::mojom::VideoBufferHandle::NewReadOnlyShmemRegion(
                  std::move(data)),
              media::mojom::VideoFrameInfo::New(
                  kMinCapturePeriod * frame_number, media::VideoFrameMetadata(),
                  kFormat, kResolution, gfx::Rect(kResolution),
                  /*natural_size=*/kResolution, kNotPremapped,
                  gfx::ColorSpace::CreateREC709(), nullptr),
              gfx::Rect(kResolution), std::move(callbacks_remote));
        },
        base::Unretained(device_.get()), std::move(region.region), frame_number,
        std::move(callbacks_remote)));
  }

  // Returns a byte value based on the given |frame_number|.
  static constexpr uint8_t GetFrameFillValue(int frame_number) {
    return (frame_number % 0x3f) << 2;
  }

  // Returns true if the |buffer| is filled with the correct byte value for the
  // given |frame_number|.
  static bool IsExpectedBufferContentForFrame(
      int frame_number,
      base::ReadOnlySharedMemoryRegion buffer) {
    const auto mapping = buffer.Map();
    const size_t frame_allocation_size =
        media::VideoFrame::AllocationSize(kFormat, kResolution);
    CHECK_LE(frame_allocation_size, mapping.size());
    const base::span<const uint8_t> src =
        mapping.GetMemoryAsSpan<const uint8_t>();
    const uint8_t expected_value = GetFrameFillValue(frame_number);
    for (size_t i = 0; i < frame_allocation_size; ++i) {
      if (src[i] != expected_value) {
        return false;
      }
    }
    return true;
  }

 protected:
  // See the threading notes at top of this file.
  BrowserTaskEnvironment task_environment_;
  NiceMock<MockFrameSinkVideoCapturer> capturer_;
  std::unique_ptr<FrameSinkVideoCaptureDevice> device_;
};

// Tests a normal session, progressing through the start, frame capture, and
// stop phases.
TEST_F(FrameSinkVideoCaptureDeviceTest, CapturesAndDeliversFrames) {
  auto receiver_ptr = std::make_unique<MockVideoFrameReceiver>();
  auto* const receiver = receiver_ptr.get();
  EXPECT_CALL(*receiver, OnStarted());
  EXPECT_CALL(*receiver, OnError(_)).Times(0);

  AllocateAndStartSynchronouslyWithExpectations(std::move(receiver_ptr));
  // From this point, there is no reason the capturer should be re-started.
  EXPECT_CALL(capturer_, MockStart(_, _)).Times(0);

  // Run 24 frames through the pipeline, one at a time. Then, run 24 more, two
  // at a time. Then, run 24 more, three at a time.
  constexpr int kNumFramesToDeliver = 24;
  constexpr int kMaxSimultaneousFrames = 3;
  int next_frame_number = 0;
  for (int in_flight_count = 1; in_flight_count <= kMaxSimultaneousFrames;
       ++in_flight_count) {
    for (int iteration = 0; iteration < kNumFramesToDeliver; ++iteration) {
      std::array<int, kMaxSimultaneousFrames> buffer_ids = {-1, -1, -1};
      std::array<MockFrameSinkVideoConsumerFrameCallbacks,
                 kMaxSimultaneousFrames>
          callbackses;

      // Simulate |in_flight_count| frame captures and expect the frames to be
      // delivered to the VideoFrameReceiver.
      const int first_frame_number = next_frame_number;
      for (int i = 0; i < in_flight_count; ++i) {
        Expectation new_buffer_called =
            EXPECT_CALL(*receiver, MockOnNewBuffer(Ge(0), NotNull()))
                .WillOnce(SaveArg<0>(&buffer_ids[i]));
        EXPECT_CALL(*receiver,
                    MockOnFrameReadyInBuffer(Eq(ByRef(buffer_ids[i])), Ge(0),
                                             NotNull(), NotNull()))
            .After(new_buffer_called);
        SimulateFrameCapture(next_frame_number, &callbackses[i]);
        ++next_frame_number;
        WAIT_FOR_DEVICE_TASKS();
      }

      // Confirm the VideoFrameReceiver was provided the correct buffer and
      // VideoFrameInfo struct for each frame in this batch.
      for (int frame_number = first_frame_number;
           frame_number < next_frame_number; ++frame_number) {
        const int buffer_id = buffer_ids[frame_number - first_frame_number];

        auto buffer = receiver->TakeBufferHandle(buffer_id);
        ASSERT_TRUE(buffer.IsValid());
        EXPECT_TRUE(
            IsExpectedBufferContentForFrame(frame_number, std::move(buffer)));

        const auto info = receiver->TakeVideoFrameInfo(buffer_id);
        ASSERT_TRUE(info);
        EXPECT_EQ(kMinCapturePeriod * frame_number, info->timestamp);
        EXPECT_EQ(kFormat, info->pixel_format);
        EXPECT_EQ(kResolution, info->coded_size);
        EXPECT_EQ(gfx::Rect(kResolution), info->visible_rect);
      }

      // Simulate the receiver providing the feedback and done notifications for
      // each frame and expect the FrameSinkVideoCaptureDevice to process these
      // notifications.
      for (int frame_number = first_frame_number;
           frame_number < next_frame_number; ++frame_number) {
        const int buffer_id = buffer_ids[frame_number - first_frame_number];
        MockFrameSinkVideoConsumerFrameCallbacks& callbacks =
            callbackses[frame_number - first_frame_number];

        media::VideoCaptureFeedback fake_feedback = media::VideoCaptureFeedback(
            static_cast<double>(frame_number) / kNumFramesToDeliver);
        fake_feedback.frame_id = receiver->TakeFeedbackId(buffer_id);

        EXPECT_CALL(callbacks, ProvideFeedback(fake_feedback));
        EXPECT_CALL(callbacks, Done());
        EXPECT_CALL(*receiver, OnBufferRetired(buffer_id));

        POST_DEVICE_METHOD_CALL(OnUtilizationReport, fake_feedback);
        receiver->ReleaseAccessPermission(buffer_id);
        WAIT_FOR_DEVICE_TASKS();
      }
    }
  }

  StopAndDeAllocateSynchronouslyWithExpectations(true /* capturer will stop */);
}

// Tests that a client request to Suspend() should stop consumption and ignore
// all refresh requests. Likewise, a client request to Resume() will
// re-establish consumption and allow refresh requests to propagate to the
// capturer again.
TEST_F(FrameSinkVideoCaptureDeviceTest, SuspendsAndResumes) {
  AllocateAndStartSynchronouslyWithExpectations(
      std::make_unique<NiceMock<MockVideoFrameReceiver>>());

  // A started device should have started the capturer, and any refresh frame
  // requests from the client should be propagated to it.
  {
    EXPECT_CALL(capturer_, RequestRefreshFrame());
    POST_DEVICE_METHOD_CALL0(RequestRefreshFrame);
    WAIT_FOR_DEVICE_TASKS();
  }

  // Simulate a client request that capture be suspended. The capturer should
  // receive a Stop() message.
  {
    EXPECT_CALL(capturer_, MockStart(_, _)).Times(0);
    EXPECT_CALL(capturer_, MockStop());
    POST_DEVICE_METHOD_CALL0(MaybeSuspend);
    WAIT_FOR_DEVICE_TASKS();
  }

  // A suspended device should not propagate any refresh frame requests.
  {
    EXPECT_CALL(capturer_, RequestRefreshFrame()).Times(0);
    POST_DEVICE_METHOD_CALL0(RequestRefreshFrame);
    WAIT_FOR_DEVICE_TASKS();
  }

  // Simulate a client request that capture be resumed. The capturer should
  // receive a Start() message.
  {
    EXPECT_CALL(capturer_, MockStart(NotNull(), _));
    EXPECT_CALL(capturer_, MockStop()).Times(0);
    POST_DEVICE_METHOD_CALL0(Resume);
    WAIT_FOR_DEVICE_TASKS();
  }

  // Now refresh frame requests should propagate again.
  {
    EXPECT_CALL(capturer_, RequestRefreshFrame());
    POST_DEVICE_METHOD_CALL0(RequestRefreshFrame);
    WAIT_FOR_DEVICE_TASKS();
  }

  StopAndDeAllocateSynchronouslyWithExpectations(true /* capturer will stop */);
}

// Tests that the FrameSinkVideoCaptureDevice will shutdown on a fatal error and
// refuse to be started again.
TEST_F(FrameSinkVideoCaptureDeviceTest, ShutsDownOnFatalError) {
  auto receiver_ptr = std::make_unique<MockVideoFrameReceiver>();
  auto* receiver = receiver_ptr.get();
  Sequence sequence;
  EXPECT_CALL(*receiver, OnStarted()).Times(0);
  EXPECT_CALL(*receiver, OnLog(StrNe(""))).InSequence(sequence);
  EXPECT_CALL(*receiver, OnError(_)).InSequence(sequence);

  AllocateAndStartSynchronouslyWithExpectations(std::move(receiver_ptr));

  // Notify the device that the target frame sink was lost. This should stop
  // consumption, unbind the capturer, log an error with the VideoFrameReceiver,
  // and destroy the VideoFrameReceiver.
  {
    EXPECT_CALL(capturer_,
                MockChangeTarget(std::optional<viz::VideoCaptureTarget>(),
                                 /*sub_capture_version=*/0));
    EXPECT_CALL(capturer_, MockStop());
    POST_DEVICE_METHOD_CALL0(OnTargetPermanentlyLost);
    WAIT_FOR_DEVICE_TASKS();
  }

  // Shutdown the device. However, the fatal error already stopped consumption,
  // so don't expect the capturer to be stopped again.
  StopAndDeAllocateSynchronouslyWithExpectations(false);

  // Now, any further attempts to start the FrameSinkVideoCaptureDevice again
  // should fail. The VideoFrameReceiver will be provided the same error
  // message.
  receiver_ptr = std::make_unique<MockVideoFrameReceiver>();
  receiver = receiver_ptr.get();
  {
    EXPECT_CALL(*receiver, OnStarted()).Times(0);
    EXPECT_CALL(*receiver, OnLog(StrNe("")));
    EXPECT_CALL(*receiver, OnError(_));
    EXPECT_CALL(capturer_, MockStart(_, _)).Times(0);

    POST_DEVICE_METHOD_CALL(AllocateAndStartWithReceiver, GetCaptureParams(),
                            std::move(receiver_ptr));
    WAIT_FOR_DEVICE_TASKS();
  }
}

// Tests that the video rotation is properly sent through metadata.
TEST_F(FrameSinkVideoCaptureDeviceTest, SetsVideoRotation) {
  auto receiver_ptr = std::make_unique<MockVideoFrameReceiver>();
  auto* const receiver = receiver_ptr.get();
  EXPECT_CALL(*receiver, OnStarted());
  EXPECT_CALL(*receiver, OnError(_)).Times(0);

  AllocateAndStartSynchronouslyWithExpectations(std::move(receiver_ptr));

  // Set the rotation to 180 degrees.
  POST_DEVICE_METHOD_CALL(SetVideoRotation, media::VIDEO_ROTATION_180);
  WAIT_FOR_DEVICE_TASKS();

  int buffer_id = -1;
  MockFrameSinkVideoConsumerFrameCallbacks callbacks;

  Expectation new_buffer_called =
      EXPECT_CALL(*receiver, MockOnNewBuffer(Ge(0), NotNull()))
          .WillOnce(SaveArg<0>(&buffer_id));
  EXPECT_CALL(*receiver, MockOnFrameReadyInBuffer(Eq(ByRef(buffer_id)), Ge(0),
                                                  NotNull(), NotNull()))
      .After(new_buffer_called);

  SimulateFrameCapture(0, &callbacks);
  WAIT_FOR_DEVICE_TASKS();

  // Confirm the VideoFrameReceiver was provided the correct metadata.
  const auto info = receiver->TakeVideoFrameInfo(buffer_id);
  ASSERT_TRUE(info);
  EXPECT_TRUE(info->metadata.transformation.has_value());
  EXPECT_EQ(media::VIDEO_ROTATION_180, info->metadata.transformation->rotation);

  media::VideoCaptureFeedback fake_feedback = media::VideoCaptureFeedback(0.0);
  fake_feedback.frame_id = receiver->TakeFeedbackId(buffer_id);
  EXPECT_CALL(callbacks, ProvideFeedback(fake_feedback));
  EXPECT_CALL(callbacks, Done());
  EXPECT_CALL(*receiver, OnBufferRetired(buffer_id));
  POST_DEVICE_METHOD_CALL(OnUtilizationReport, fake_feedback);
  receiver->ReleaseAccessPermission(buffer_id);
  auto buffer = receiver->TakeBufferHandle(buffer_id);
  WAIT_FOR_DEVICE_TASKS();

  StopAndDeAllocateSynchronouslyWithExpectations(true /* capturer will stop */);
}

}  // namespace
}  // namespace content
