chromium/chromeos/ash/components/camera_presence_notifier/camera_presence_notifier_unittest.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 "chromeos/ash/components/camera_presence_notifier/camera_presence_notifier.h"

#include <string>

#include "ash/capture_mode/fake_video_source_provider.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "content/public/browser/video_capture_service.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "services/video_capture/public/mojom/video_capture_service.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

constexpr char kFakeCameraDeviceId[] = "/dev/videoX";
constexpr char kFakeCameraDisplayName[] = "Fake Camera";
constexpr char kFakeCameraModelId[] = "0def:c000";

}  // namespace

class FakeVideoCaptureService
    : public video_capture::mojom::VideoCaptureService {
 public:
  FakeVideoCaptureService() = default;
  ~FakeVideoCaptureService() override = default;

  void AddFakeCamera() {
    const std::string camera_id =
        kFakeCameraDeviceId + base::NumberToString(next_id_++);
    used_ids_.push_back(camera_id);
    fake_provider_.AddFakeCameraWithoutNotifying(
        camera_id, kFakeCameraDisplayName, kFakeCameraModelId,
        media::MEDIA_VIDEO_FACING_NONE);
  }

  void RemoveFakeCamera() {
    ASSERT_FALSE(used_ids_.empty());
    fake_provider_.RemoveFakeCameraWithoutNotifying(used_ids_.back());
    used_ids_.pop_back();
  }

  // mojom::VideoCaptureService:
  void InjectGpuDependencies(
      mojo::PendingRemote<video_capture::mojom::AcceleratorFactory>
          accelerator_factory) override {}

  void ConnectToCameraAppDeviceBridge(
      mojo::PendingReceiver<cros::mojom::CameraAppDeviceBridge> receiver)
      override {}

  void BindVideoCaptureDeviceFactory(
      mojo::PendingReceiver<crosapi::mojom::VideoCaptureDeviceFactory> receiver)
      override {}

  void ConnectToVideoSourceProvider(
      mojo::PendingReceiver<video_capture::mojom::VideoSourceProvider> receiver)
      override {
    fake_provider_.Bind(std::move(receiver));
  }

  void BindControlsForTesting(
      mojo::PendingReceiver<video_capture::mojom::TestingControls> receiver)
      override {}

 private:
  FakeVideoSourceProvider fake_provider_;
  std::vector<std::string> used_ids_;
  int next_id_{0};
};

class CameraPresenceNotifierTest : public testing::Test {
 public:
  CameraPresenceNotifierTest() = default;

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

  ~CameraPresenceNotifierTest() override = default;

  void AddFakeCamera() { fake_service_.AddFakeCamera(); }

  void RemoveFakeCamera() { fake_service_.RemoveFakeCamera(); }

  // Advances time past the poll interval.
  void StepClock() { task_environment_.FastForwardBy(base::Seconds(10)); }

  // testing::Test:
  void SetUp() override {
    content::OverrideVideoCaptureServiceForTesting(&fake_service_);
  }

  void TearDown() override {
    content::OverrideVideoCaptureServiceForTesting(nullptr);
  }

 private:
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  FakeVideoCaptureService fake_service_;
};

// Tests that the observer of CameraPresenceNotifier works correctly when the
// camera is added/removed (using the presence callback).
TEST_F(CameraPresenceNotifierTest, TestPresenceObserver) {
  std::vector<bool> values;
  CameraPresenceNotifier::CameraPresenceCallback callback = base::BindRepeating(
      [](std::vector<bool>* values, bool camera_is_present) {
        values->push_back(camera_is_present);
      },
      &values);

  CameraPresenceNotifier notifier(callback);

  // No events before start.
  ASSERT_TRUE(values.empty());
  notifier.Start();
  StepClock();
  // The first result should be false since there are no available cameras.
  ASSERT_EQ(1U, values.size());
  EXPECT_FALSE(values.back());

  // Advance clock to verify that unchanged values don't cause callbacks.
  StepClock();
  ASSERT_EQ(1U, values.size());

  // Add a camera.
  AddFakeCamera();
  StepClock();
  ASSERT_EQ(2U, values.size());
  // There is a camera now.
  EXPECT_TRUE(values.back());

  // Camera removed. Next callback is false again.
  RemoveFakeCamera();
  StepClock();
  ASSERT_EQ(3U, values.size());
  EXPECT_FALSE(values.back());
}

// Tests that the observer of CameraPresenceNotifier works correctly when the
// cameras are added/removed (using the count callback).
TEST_F(CameraPresenceNotifierTest, TestCountObserver) {
  std::vector<int> values;
  CameraPresenceNotifier::CameraCountCallback callback = base::BindRepeating(
      [](std::vector<int>* values, int camera_count) {
        values->push_back(camera_count);
      },
      &values);

  CameraPresenceNotifier notifier(callback);

  // No events before start.
  ASSERT_TRUE(values.empty());
  notifier.Start();
  StepClock();
  // The first result should be 0 since there are no available cameras.
  ASSERT_EQ(1U, values.size());
  EXPECT_EQ(0, values.back());

  // Advance clock to verify that unchanged values don't cause callbacks.
  StepClock();
  ASSERT_EQ(1U, values.size());

  // Add a camera.
  AddFakeCamera();
  StepClock();
  ASSERT_EQ(2U, values.size());
  // There is a camera now.
  EXPECT_EQ(1, values.back());

  // Camera removed.
  RemoveFakeCamera();
  StepClock();
  ASSERT_EQ(3U, values.size());
  EXPECT_EQ(0, values.back());

  // Add 1 camera.
  AddFakeCamera();
  StepClock();
  ASSERT_EQ(4U, values.size());
  EXPECT_EQ(1, values.back());
  // Add 2nd camera.
  AddFakeCamera();
  StepClock();
  ASSERT_EQ(5U, values.size());
  EXPECT_EQ(2, values.back());

  // Remove 2nd camera
  RemoveFakeCamera();
  StepClock();
  ASSERT_EQ(6U, values.size());
  EXPECT_EQ(1, values.back());
  // Remove 1st camera
  RemoveFakeCamera();
  StepClock();
  ASSERT_EQ(7U, values.size());
  EXPECT_EQ(0, values.back());
}

}  // namespace ash