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

#include "base/test/mock_devices_changed_observer.h"
#include "content/browser/renderer_host/media/media_devices_manager.h"
#include "content/public/browser/video_capture_service.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/video_capture/public/cpp/mock_video_capture_service.h"
#include "services/video_capture/public/cpp/mock_video_source_provider.h"
#include "services/video_capture/public/mojom/video_source_provider.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;

namespace content {

class MockVideoCaptureDevicesChangedObserver
    : public MediaDevicesManager::VideoCaptureDevicesChangedObserver {
 public:
  MockVideoCaptureDevicesChangedObserver()
      : VideoCaptureDevicesChangedObserver(
            /*invalidate_cache_cb=*/base::BindRepeating(
                &MockVideoCaptureDevicesChangedObserver::HandleDisconnect,
                base::Unretained(this),
                MediaDeviceType::kMediaVideoInput),
            /*enumerate_system_devices_cb=*/
            base::BindRepeating(
                &MockVideoCaptureDevicesChangedObserver::HandleDevicesChanged,
                base::Unretained(this),
                MediaDeviceType::kMediaVideoInput),
            /*listener_cb=*/base::BindRepeating([] {
              if (auto* monitor = base::SystemMonitor::Get()) {
                monitor->ProcessDevicesChanged(
                    base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
              }
            })),
        source_provider_receiver_(&mock_source_provider_) {
    OverrideVideoCaptureServiceForTesting(&mock_video_capture_service_);
    ON_CALL(mock_video_capture_service_, DoConnectToVideoSourceProvider(_))
        .WillByDefault(
            [this](
                mojo::PendingReceiver<video_capture::mojom::VideoSourceProvider>
                    receiver) {
              if (source_provider_receiver_.is_bound()) {
                source_provider_receiver_.reset();
              }
              source_provider_receiver_.Bind(std::move(receiver));
              base::RunLoop().RunUntilIdle();
            });

    EnsureConnectedToService();
    base::RunLoop().RunUntilIdle();
  }

  void CrashService() { source_provider_receiver_.reset(); }

  MOCK_METHOD1(HandleDisconnect, void(MediaDeviceType type));
  MOCK_METHOD1(HandleDevicesChanged, void(MediaDeviceType type));

  mojo::Receiver<video_capture::mojom::VideoSourceProvider>
      source_provider_receiver_;
  video_capture::MockVideoCaptureService mock_video_capture_service_;
  video_capture::MockVideoSourceProvider mock_source_provider_;
};

class DevicesChangedObserverTest : public testing::Test {
 public:
  DevicesChangedObserverTest()
      : mock_video_capture_service_device_changed_observer_(
            std::make_unique<MockVideoCaptureDevicesChangedObserver>()) {
    system_monitor_.AddDevicesChangedObserver(&observer);
  }

  void RaiseVirtualDeviceChangeEvent() {
    mock_video_capture_service_device_changed_observer_->mock_source_provider_
        .RaiseVirtualDeviceChangeEvent();
  }

  void RaiseDeviceChangeEvent() {
    mock_video_capture_service_device_changed_observer_->mock_source_provider_
        .RaiseDeviceChangeEvent();
  }

  void TearDown() override {
    system_monitor_.RemoveDevicesChangedObserver(&observer);
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  base::SystemMonitor system_monitor_;
  base::MockDevicesChangedObserver observer;
  std::unique_ptr<MockVideoCaptureDevicesChangedObserver>
      mock_video_capture_service_device_changed_observer_;
};

TEST_F(DevicesChangedObserverTest,
       RegisteredObserverReceivesDeviceChangeEvents) {
  EXPECT_CALL(observer,
              OnDevicesChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE))
      .Times(1);
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();

  // No OnDevicesChanged event should be observed when a virtual device change
  // event occurs.
  RaiseVirtualDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();
}

TEST_F(DevicesChangedObserverTest, ServiceCrashes) {
  EXPECT_CALL(observer,
              OnDevicesChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE))
      .Times(2);
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();

  // Crashing the service should call invalidate_cache_cb (calling
  // |HandleDisconnect|) and then enumerate_system_devices_cb (calling
  // |HandleDevicesChanged|). But device change events are still registered
  // because the OnConnectionError() function calls EnsureConnectedToService().
  {
    testing::InSequence s;
    EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
                HandleDisconnect(MediaDeviceType::kMediaVideoInput))
        .Times(1);
    EXPECT_CALL(mock_video_capture_service_device_changed_observer_
                    ->mock_video_capture_service_,
                DoConnectToVideoSourceProvider(_))
        .Times(1);
    EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
                HandleDevicesChanged(MediaDeviceType::kMediaVideoInput))
        .Times(1);
  }
  mock_video_capture_service_device_changed_observer_->CrashService();
  base::RunLoop().RunUntilIdle();
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();
}

TEST_F(DevicesChangedObserverTest, ServiceDisconnect) {
  EXPECT_CALL(observer,
              OnDevicesChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE))
      .Times(1);
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();

  // Disconnect the service manually should not call invalidate_cache_cb so no
  // |HandleDisconnect|. Device change events will not be raised after
  // disconnect.
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDisconnect(MediaDeviceType::kMediaVideoInput))
      .Times(0);
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDevicesChanged(MediaDeviceType::kMediaVideoInput))
      .Times(0);
  mock_video_capture_service_device_changed_observer_
      ->DisconnectVideoSourceProvider();
  base::RunLoop().RunUntilIdle();
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();
}

TEST_F(DevicesChangedObserverTest, ConnectServiceAfterCrash) {
  EXPECT_CALL(observer,
              OnDevicesChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE))
      .Times(2);
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();

  // Crashing the service should call invalidate_cache_cb (calling
  // |HandleDisconnect|) and then enumerate_system_devices_cb (calling
  // |HandleDevicesChanged|). Manually call EnsureConnectedToService won't
  // affect current mojo bound state.
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDisconnect(MediaDeviceType::kMediaVideoInput))
      .Times(1);
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDevicesChanged(MediaDeviceType::kMediaVideoInput))
      .Times(1);
  mock_video_capture_service_device_changed_observer_->CrashService();
  base::RunLoop().RunUntilIdle();
  mock_video_capture_service_device_changed_observer_
      ->EnsureConnectedToService();
  base::RunLoop().RunUntilIdle();
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();
}

TEST_F(DevicesChangedObserverTest, ConnectServiceAfterDisconnect) {
  EXPECT_CALL(observer,
              OnDevicesChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE))
      .Times(2);
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();

  // Disconnect the service manually should not call invalidate_cache_cb so no
  // |HandleDisconnect|. Device change events will be raised after service
  // is connected.
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDisconnect(MediaDeviceType::kMediaVideoInput))
      .Times(0);
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDevicesChanged(MediaDeviceType::kMediaVideoInput))
      .Times(0);
  mock_video_capture_service_device_changed_observer_
      ->DisconnectVideoSourceProvider();
  base::RunLoop().RunUntilIdle();
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();
  mock_video_capture_service_device_changed_observer_
      ->EnsureConnectedToService();
  base::RunLoop().RunUntilIdle();
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();
}

TEST_F(DevicesChangedObserverTest, ConnectServiceTwiceAfterDisconnect) {
  EXPECT_CALL(observer,
              OnDevicesChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE))
      .Times(2);
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();

  // Disconnect the service manually should not call invalidate_cache_cb so no
  // |HandleDisconnect|. Device change events will be raised after service
  // is connected.
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDisconnect(MediaDeviceType::kMediaVideoInput))
      .Times(0);
  EXPECT_CALL(*mock_video_capture_service_device_changed_observer_,
              HandleDevicesChanged(MediaDeviceType::kMediaVideoInput))
      .Times(0);
  mock_video_capture_service_device_changed_observer_
      ->DisconnectVideoSourceProvider();
  base::RunLoop().RunUntilIdle();
  mock_video_capture_service_device_changed_observer_
      ->EnsureConnectedToService();
  mock_video_capture_service_device_changed_observer_
      ->EnsureConnectedToService();
  base::RunLoop().RunUntilIdle();
  RaiseDeviceChangeEvent();
  base::RunLoop().RunUntilIdle();
}

}  // namespace content
