chromium/chrome/browser/ash/video_conference/video_conference_manager_ash_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 "chrome/browser/ash/video_conference/video_conference_manager_ash.h"

#include <memory>
#include <utility>
#include <vector>

#include "ash/system/video_conference/video_conference_common.h"
#include "base/memory/raw_ref.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

class FakeVideoConferenceManagerAsh : public VideoConferenceManagerAsh {
 public:
  VideoConferenceMediaState& state() { return state_; }

 protected:
  void SendUpdatedState() override { state_ = GetAggregatedState(); }

 private:
  VideoConferenceMediaState state_;
};

class FakeVcManagerCppClient
    : public crosapi::mojom::VideoConferenceManagerClient {
 public:
  explicit FakeVcManagerCppClient(FakeVideoConferenceManagerAsh& vc_manager)
      : id_(base::UnguessableToken::Create()), vc_manager_(vc_manager) {}
  FakeVcManagerCppClient(const FakeVcManagerCppClient&) = delete;
  FakeVcManagerCppClient& operator=(const FakeVcManagerCppClient&) = delete;
  ~FakeVcManagerCppClient() override { vc_manager_->UnregisterClient(id_); }

  // crosapi::mojom::VideoConferenceManagerClient overrides
  void GetMediaApps(
      crosapi::mojom::VideoConferenceManagerClient::GetMediaAppsCallback
          callback) override {
    std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> apps;

    for (auto& app : apps_) {
      apps.push_back(app->Clone());
    }

    std::move(callback).Run(std::move(apps));
  }

  void ReturnToApp(const base::UnguessableToken& id,
                   ReturnToAppCallback callback) override {}

  void SetSystemMediaDeviceStatus(
      crosapi::mojom::VideoConferenceMediaDevice device,
      bool disabled,
      SetSystemMediaDeviceStatusCallback callback) override {}

  void StopAllScreenShare() override {}

  // Public for testing.
  base::UnguessableToken id_;
  std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> apps_;
  const raw_ref<FakeVideoConferenceManagerAsh> vc_manager_;
};

class VideoConferenceManagerAshTest : public testing::Test {
 public:
  struct CaptureStatus {
    bool is_capturing_camera = false;
    bool is_capturing_microphone = false;
    bool is_capturing_screen = false;
  };

  FakeVideoConferenceManagerAsh& vc_manager() { return vc_manager_; }

  // Aggregates capturing statuses from all media apps into a |CaptureStatus|
  // and returns it.
  CaptureStatus GetAggregatedCaptureStatus(
      VideoConferenceManagerAsh::MediaApps apps) {
    bool is_capturing_camera = false;
    bool is_capturing_microphone = false;
    bool is_capturing_screen = false;

    for (auto& app : apps) {
      is_capturing_camera |= app->is_capturing_camera;
      is_capturing_microphone |= app->is_capturing_microphone;
      is_capturing_screen |= app->is_capturing_screen;
    }

    return {is_capturing_camera, is_capturing_microphone, is_capturing_screen};
  }

  // Public because this is test code.
  base::test::TaskEnvironment task_environment_;

 private:
  FakeVideoConferenceManagerAsh vc_manager_;
};

// Tests |VideoConferenceManagerAsh::GetMediaApps| returns correct aggregated
// results from all VcClients.
TEST_F(VideoConferenceManagerAshTest, VcManagerGetMediaApps) {
  const auto now = base::Time::Now();
  const auto duration = base::Seconds(1);

  std::unique_ptr<FakeVcManagerCppClient> client1 =
      std::make_unique<FakeVcManagerCppClient>(vc_manager());
  client1->apps_.push_back(crosapi::mojom::VideoConferenceMediaAppInfo::New(
      /*id=*/base::UnguessableToken::Create(),
      /*last_activity_time=*/now,
      /*is_capturing_camera=*/false, /*is_capturing_microphone=*/false,
      /*is_capturing_screen=*/true, /*title=*/u"Test App0",
      /*url=*/std::nullopt));

  vc_manager().RegisterCppClient(client1.get(), client1->id_);

  // Basic functioning.
  base::RunLoop run_loop1;
  vc_manager().GetMediaApps(base::BindLambdaForTesting(
      [&](VideoConferenceManagerAsh::MediaApps apps) {
        EXPECT_EQ(apps.size(), 1u);
        EXPECT_EQ(apps[0]->title, u"Test App0");

        auto status = GetAggregatedCaptureStatus(std::move(apps));

        EXPECT_FALSE(status.is_capturing_camera);
        EXPECT_FALSE(status.is_capturing_microphone);
        EXPECT_TRUE(status.is_capturing_screen);
        run_loop1.Quit();
      }));
  run_loop1.Run();

  client1->apps_.push_back(crosapi::mojom::VideoConferenceMediaAppInfo::New(
      /*id=*/base::UnguessableToken::Create(),
      /*last_activity_time=*/now + duration * 10,
      /*is_capturing_camera=*/true, /*is_capturing_microphone=*/false,
      /*is_capturing_screen=*/true, /*title=*/u"Test App1",
      /*url=*/std::nullopt));

  base::RunLoop run_loop2;
  vc_manager().GetMediaApps(base::BindLambdaForTesting(
      [&](VideoConferenceManagerAsh::MediaApps apps) {
        EXPECT_EQ(apps.size(), 2UL);
        EXPECT_EQ(apps[0]->title, u"Test App1");
        EXPECT_EQ(apps[1]->title, u"Test App0");

        auto status = GetAggregatedCaptureStatus(std::move(apps));

        EXPECT_TRUE(status.is_capturing_camera);
        EXPECT_FALSE(status.is_capturing_microphone);
        EXPECT_TRUE(status.is_capturing_screen);
        run_loop2.Quit();
      }));
  run_loop2.Run();

  // Multiple clients.
  {
    std::unique_ptr<FakeVcManagerCppClient> client2 =
        std::make_unique<FakeVcManagerCppClient>(vc_manager());
    client2->apps_.push_back(crosapi::mojom::VideoConferenceMediaAppInfo::New(
        /*id=*/base::UnguessableToken::Create(),
        /*last_activity_time=*/now + duration * 2,
        /*is_capturing_camera=*/false, /*is_capturing_microphone=*/true,
        /*is_capturing_screen=*/false, /*title=*/u"Test App2",
        /*url=*/std::nullopt));

    vc_manager().RegisterCppClient(client2.get(), client2->id_);

    base::RunLoop run_loop3;
    vc_manager().GetMediaApps(base::BindLambdaForTesting(
        [&](VideoConferenceManagerAsh::MediaApps apps) {
          EXPECT_EQ(apps.size(), 3UL);
          EXPECT_EQ(apps[0]->title, u"Test App1");
          EXPECT_EQ(apps[1]->title, u"Test App2");
          EXPECT_EQ(apps[2]->title, u"Test App0");

          auto status = GetAggregatedCaptureStatus(std::move(apps));

          EXPECT_TRUE(status.is_capturing_camera);
          EXPECT_TRUE(status.is_capturing_microphone);
          EXPECT_TRUE(status.is_capturing_screen);
          run_loop3.Quit();
        }));
    run_loop3.Run();
  }

  client1->apps_.pop_back();

  // Expect disconnecting clients and modifying internal client state to work
  // correctly.
  base::RunLoop run_loop4;
  vc_manager().GetMediaApps(base::BindLambdaForTesting(
      [&](VideoConferenceManagerAsh::MediaApps apps) {
        EXPECT_EQ(apps.size(), 1u);

        auto status = GetAggregatedCaptureStatus(std::move(apps));

        EXPECT_FALSE(status.is_capturing_camera);
        EXPECT_FALSE(status.is_capturing_microphone);
        EXPECT_TRUE(status.is_capturing_screen);
        run_loop4.Quit();
      }));
  run_loop4.Run();
}

// Tests VcManager state correctly updates after |NotifyMediaUsageUpdate| calls.
TEST_F(VideoConferenceManagerAshTest, VcManagerNotifyMediaUsageUpdate) {
  // crosapi::mojom::VideoConferenceMediaUsageStatus
  std::unique_ptr<FakeVcManagerCppClient> client1 =
      std::make_unique<FakeVcManagerCppClient>(vc_manager());
  vc_manager().RegisterCppClient(client1.get(), client1->id_);

  // Expect initial values are all false.
  EXPECT_FALSE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_FALSE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_FALSE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);

  auto success_callback =
      base::BindRepeating([](bool success) { EXPECT_TRUE(success); });

  // Basic functioning.

  base::RunLoop run_loop1;
  vc_manager().NotifyMediaUsageUpdate(
      crosapi::mojom::VideoConferenceMediaUsageStatus::New(
          /*client_id=*/client1->id_, /*has_media_app=*/true,
          /*has_camera_permission=*/false,
          /*has_microphone_permission=*/true, /*is_capturing_camera=*/false,
          /*is_capturing_microphone=*/true, /*is_capturing_screen=*/false),
      base::BindLambdaForTesting([&](bool success) {
        EXPECT_TRUE(success);
        run_loop1.Quit();
      }));
  run_loop1.Run();

  EXPECT_TRUE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_TRUE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_TRUE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);

  // Multiple clients.
  {
    std::unique_ptr<FakeVcManagerCppClient> client2 =
        std::make_unique<FakeVcManagerCppClient>(vc_manager());
    vc_manager().RegisterCppClient(client2.get(), client2->id_);

    base::RunLoop run_loop2;
    vc_manager().NotifyMediaUsageUpdate(
        crosapi::mojom::VideoConferenceMediaUsageStatus::New(
            /*client_id=*/client2->id_, /*has_media_app=*/true,
            /*has_camera_permission=*/true,
            /*has_microphone_permission=*/true,
            /*is_capturing_camera=*/true,
            /*is_capturing_microphone=*/true,
            /*is_capturing_screen=*/true),
        base::BindLambdaForTesting([&](bool success) {
          EXPECT_TRUE(success);
          run_loop2.Quit();
        }));
    run_loop2.Run();

    EXPECT_TRUE(vc_manager().state().has_media_app);
    EXPECT_TRUE(vc_manager().state().has_camera_permission);
    EXPECT_TRUE(vc_manager().state().has_microphone_permission);
    EXPECT_TRUE(vc_manager().state().is_capturing_camera);
    EXPECT_TRUE(vc_manager().state().is_capturing_microphone);
    EXPECT_TRUE(vc_manager().state().is_capturing_screen);
  }

  // Expect status is updated after a client disconnects.
  EXPECT_TRUE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_TRUE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_TRUE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);

  // Expect previously true fields are correctly reset on later
  // |NotifyMediaUsageUpdate| calls.
  base::RunLoop run_loop3;
  vc_manager().NotifyMediaUsageUpdate(
      crosapi::mojom::VideoConferenceMediaUsageStatus::New(
          /*client_id=*/client1->id_, /*has_media_app=*/true,
          /*has_camera_permission=*/false,
          /*has_microphone_permission=*/true, /*is_capturing_camera=*/false,
          /*is_capturing_microphone=*/false,
          /*is_capturing_screen=*/false),
      base::BindLambdaForTesting([&](bool success) {
        EXPECT_TRUE(success);
        run_loop3.Quit();
      }));
  run_loop3.Run();

  EXPECT_TRUE(vc_manager().state().has_media_app);
  EXPECT_FALSE(vc_manager().state().has_camera_permission);
  EXPECT_TRUE(vc_manager().state().has_microphone_permission);
  EXPECT_FALSE(vc_manager().state().is_capturing_camera);
  EXPECT_FALSE(vc_manager().state().is_capturing_microphone);
  EXPECT_FALSE(vc_manager().state().is_capturing_screen);
}

}  // namespace ash