chromium/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc

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

#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"

#include "ash/constants/ash_features.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/components/mojo_service_manager/connection.h"
#include "components/device_event_log/device_event_log.h"
#include "media/capture/video/chromeos/mojom/camera_common.mojom.h"
#include "media/capture/video/chromeos/mojom/cros_camera_client.mojom.h"
#include "media/capture/video/chromeos/mojom/cros_camera_service.mojom.h"
#include "media/capture/video/chromeos/mojom/effects_pipeline.mojom.h"
#include "media/capture/video/chromeos/mojom/video_capture_device_info_monitor.mojom.h"
#include "media/capture/video/chromeos/video_capture_features_chromeos.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "third_party/cros_system_api/mojo/service_constants.h"

namespace media {

namespace {

const base::FilePath::CharType kForceEnableAePath[] =
    "/run/camera/force_enable_face_ae";
const base::FilePath::CharType kForceDisableAePath[] =
    "/run/camera/force_disable_face_ae";
const base::FilePath::CharType kForceEnableAutoFramingPath[] =
    "/run/camera/force_enable_auto_framing";
const base::FilePath::CharType kForceDisableAutoFramingPath[] =
    "/run/camera/force_disable_auto_framing";
const base::FilePath::CharType kForceEnableEffectsPath[] =
    "/run/camera/force_enable_effects";
const base::FilePath::CharType kForceDisableEffectsPath[] =
    "/run/camera/force_disable_effects";
const base::FilePath::CharType kForceEnableSuperResPath[] =
    "/run/camera/force_enable_super_res";
const base::FilePath::CharType kForceDisableSuperResPath[] =
    "/run/camera/force_disable_super_res";
const base::FilePath::CharType kEnableRetouchWithRelightPath[] =
    "/run/camera/enable_retouch_with_relight";
const base::FilePath::CharType kEnableOnlyRetouchPath[] =
    "/run/camera/enable_only_retouch";

void CreateFile(const std::vector<std::string>& paths,
                const std::vector<bool>& should_create) {
  CHECK(paths.size() == should_create.size());
  for (size_t i = 0; i < paths.size(); ++i) {
    base::FilePath path(paths[i]);
    if (should_create[i]) {
      if (!base::PathExists(path)) {
        base::File file(
            path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
        file.Close();
      }
    } else if (!base::DeleteFile(path)) {
      LOG(WARNING) << "CameraHalDispatcherImpl Error: can't  delete " << path;
    }
  }
}

void CreateEnableDisableFile(const std::string& enable_path,
                             const std::string& disable_path,
                             bool should_enable,
                             bool should_remove_both) {
  base::FilePath enable_file_path(enable_path);
  base::FilePath disable_file_path(disable_path);

  // Removing enable file if the target is to disable or remove both.
  if ((!should_enable || should_remove_both) &&
      !base::DeleteFile(enable_file_path)) {
    LOG(WARNING) << "CameraHalDispatcherImpl Error: can't  delete "
                 << enable_file_path;
  }

  // Removing disable file if the target is to enable or remove both.
  if ((should_enable || should_remove_both) &&
      !base::DeleteFile(disable_file_path)) {
    LOG(WARNING) << "CameraHalDispatcherImpl Error: can't delete "
                 << disable_file_path;
  }

  if (should_remove_both) {
    return;
  }

  const base::FilePath& new_file =
      should_enable ? enable_file_path : disable_file_path;

  // Adding enable/disable file if it does not exist yet.
  if (!base::PathExists(new_file)) {
    base::File file(new_file,
                    base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
    file.Close();
  }
}

bool HasCrosCameraTest() {
  static constexpr char kCrosCameraTestPath[] =
      "/usr/local/bin/cros_camera_test";

  base::FilePath path(kCrosCameraTestPath);
  return base::PathExists(path);
}

class MojoCameraClientObserver : public CameraClientObserver {
 public:
  MojoCameraClientObserver() = delete;

  explicit MojoCameraClientObserver(
      mojo::PendingRemote<cros::mojom::CameraHalClient> client,
      cros::mojom::CameraClientType type,
      base::UnguessableToken auth_token)
      : CameraClientObserver(type, std::move(auth_token)),
        client_(std::move(client)) {}

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

  void OnChannelCreated(
      mojo::PendingRemote<cros::mojom::CameraModule> camera_module) override {
    client_->SetUpChannel(std::move(camera_module));
  }

  mojo::Remote<cros::mojom::CameraHalClient>& client() { return client_; }

 private:
  mojo::Remote<cros::mojom::CameraHalClient> client_;
};

}  // namespace

CameraClientObserver::~CameraClientObserver() = default;

bool CameraClientObserver::Authenticate(TokenManager* token_manager) {
  auto authenticated_type =
      token_manager->AuthenticateClient(type_, auth_token_);
  if (!authenticated_type) {
    return false;
  }
  type_ = authenticated_type.value();
  return true;
}

class CameraHalDispatcherImpl::VCDInfoObserverImpl
    : public cros::mojom::VideoCaptureDeviceInfoObserver {
 public:
  using OnGetCameraIdToDeviceIdCallback =
      base::RepeatingCallback<void(int32_t, const std::string&)>;
  explicit VCDInfoObserverImpl(
      OnGetCameraIdToDeviceIdCallback on_get_camera_id_to_device_id_callback)
      : on_get_camera_id_to_device_id_callback_(
            on_get_camera_id_to_device_id_callback) {
    mojo_service_manager_observer_ = MojoServiceManagerObserver::Create(
        chromeos::mojo_services::kVideoCaptureDeviceInfoMonitor,
        base::BindRepeating(
            &VCDInfoObserverImpl::ConnectToVCDInfoMonitorService,
            weak_factory_.GetWeakPtr()),
        base::DoNothing());
  }

  ~VCDInfoObserverImpl() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  }

  // cros::mojom::VideoCaptureDeviceInfoObserver overrides.
  void OnGetCameraIdToDeviceIdMapping(int32_t camera_id,
                                      const std::string& device_id) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    on_get_camera_id_to_device_id_callback_.Run(camera_id, device_id);
  }

  void ConnectToVCDInfoMonitorService() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    ash::mojo_service_manager::GetServiceManagerProxy()->Request(
        /*service_name=*/chromeos::mojo_services::
            kVideoCaptureDeviceInfoMonitor,
        std::nullopt,
        vcd_info_monitor_.BindNewPipeAndPassReceiver().PassPipe());
    vcd_info_monitor_->AddVideoCaptureDeviceInfoObserver(
        observer_receiver_.BindNewPipeAndPassRemote());
    vcd_info_monitor_.set_disconnect_handler(base::BindOnce(
        &VCDInfoObserverImpl::ResetMojoInterface, base::Unretained(this)));
  }

  void ResetMojoInterface() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    vcd_info_monitor_.reset();
    observer_receiver_.reset();
  }

 public:
  OnGetCameraIdToDeviceIdCallback on_get_camera_id_to_device_id_callback_;

  std::unique_ptr<MojoServiceManagerObserver> mojo_service_manager_observer_;

  SEQUENCE_CHECKER(sequence_checker_);

  mojo::Remote<cros::mojom::VideoCaptureDeviceInfoMonitor> vcd_info_monitor_;

  mojo::Receiver<cros::mojom::VideoCaptureDeviceInfoObserver>
      observer_receiver_{this};

  base::WeakPtrFactory<VCDInfoObserverImpl> weak_factory_{this};
};

// static
CameraHalDispatcherImpl* CameraHalDispatcherImpl::GetInstance() {
  return base::Singleton<CameraHalDispatcherImpl>::get();
}

bool CameraHalDispatcherImpl::StartThreads() {
  DCHECK(!proxy_thread_.IsRunning());

  if (!proxy_thread_.Start()) {
    LOG(ERROR) << "Failed to start proxy thread";
    return false;
  }
  proxy_task_runner_ = proxy_thread_.task_runner();
  return true;
}

void CameraHalDispatcherImpl::BindCameraServiceOnProxyThread(
    mojo::PendingRemote<cros::mojom::CrosCameraService> camera_service) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  DCHECK(!camera_service_.is_bound());

  CAMERA_LOG(EVENT) << "Connected to cros-camera.";
  camera_service_.Bind(std::move(camera_service));
  camera_service_.set_disconnect_handler(
      base::BindOnce(&CameraHalDispatcherImpl::OnCameraServiceConnectionError,
                     base::Unretained(this)));
  if (auto_framing_supported_callback_) {
    camera_service_->GetAutoFramingSupported(
        std::move(auto_framing_supported_callback_));
  }
  camera_service_->SetAutoFramingState(current_auto_framing_state_);

  // Should only be called when an effect is set.
  if (!initial_effects_.is_null() || !current_effects_.is_null()) {
    // If current_effects_ is set, then a newer effect was applied since
    // the initial setup and we should use that, as the camera server
    // may have crashed and restarted.
    cros::mojom::EffectsConfigPtr& config =
        current_effects_.is_null() ? initial_effects_ : current_effects_;

    SetCameraEffectsOnProxyThread(config.Clone(), /*is_from_register=*/true);
  }
  camera_service_->AddCrosCameraServiceObserver(
      camera_service_observer_receiver_.BindNewPipeAndPassRemote());
  // Set up the Mojo channels for clients which registered before cros camera
  // service starts or that have disconnected from the camera module because the
  // cros camera service stopped.
  for (CameraClientObserver* client_observer : client_observers_) {
    EstablishMojoChannel(client_observer);
  }
}

void CameraHalDispatcherImpl::TryConnectToCameraService() {
  CHECK(ash::mojo_service_manager::IsServiceManagerBound());

  mojo::PendingRemote<cros::mojom::CrosCameraService> camera_service;
  ash::mojo_service_manager::GetServiceManagerProxy()->Request(
      chromeos::mojo_services::kCrosCameraService, std::nullopt,
      camera_service.InitWithNewPipeAndPassReceiver().PassPipe());
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&CameraHalDispatcherImpl::BindCameraServiceOnProxyThread,
                     base::Unretained(this), std::move(camera_service)));
}

bool CameraHalDispatcherImpl::Start() {
  DCHECK(!IsStarted());
  if (!StartThreads()) {
    return false;
  }

  const base::CommandLine* command_line =
      base::CommandLine::ForCurrentProcess();

  CreateEnableDisableFile(
      kForceEnableAePath, kForceDisableAePath,
      /*should_enable=*/
      command_line->GetSwitchValueASCII(media::switches::kForceControlFaceAe) ==
          "enable",
      /*should_remove_both=*/
      !command_line->HasSwitch(media::switches::kForceControlFaceAe));

  CreateEnableDisableFile(
      kForceEnableAutoFramingPath, kForceDisableAutoFramingPath,
      /*should_enable=*/
      command_line->GetSwitchValueASCII(switches::kAutoFramingOverride) ==
          switches::kAutoFramingForceEnabled,
      /*should_remove_both=*/
      !command_line->HasSwitch(media::switches::kAutoFramingOverride));

  CreateEnableDisableFile(
      kForceEnableEffectsPath, kForceDisableEffectsPath,
      /*should_enable=*/ash::features::IsVideoConferenceEnabled(),
      /*should_remove_both=*/false);

  CreateEnableDisableFile(
      kForceEnableSuperResPath, kForceDisableSuperResPath,
      /*should_enable=*/
      command_line->GetSwitchValueASCII(switches::kCameraSuperResOverride) !=
          switches::kCameraSuperResForceDisabled,
      /*should_remove_both=*/false);

  std::string face_retouch_override =
      command_line->GetSwitchValueASCII(switches::kFaceRetouchOverride);
  CreateFile(
      {
          kEnableOnlyRetouchPath,
          kEnableRetouchWithRelightPath,
      },
      {
          face_retouch_override ==
              switches::kFaceRetouchForceEnabledWithoutRelighting,
          face_retouch_override ==
              switches::kFaceRetouchForceEnabledWithRelighting,
      });

  base::WaitableEvent started;
  // It's important we generate tokens before creating the socket, because
  // once it is available, everyone connecting to socket would start fetching
  // tokens.
  if (HasCrosCameraTest() && !token_manager_.GenerateTestClientToken()) {
    LOG(ERROR) << "Failed to generate token for test client";
    return false;
  }

  mojo_service_manager_observer_ = MojoServiceManagerObserver::Create(
      chromeos::mojo_services::kCrosCameraService,
      base::BindRepeating(&CameraHalDispatcherImpl::TryConnectToCameraService,
                          weak_factory_.GetWeakPtr()),
      base::DoNothing());

  vcd_info_observer_impl_ = std::make_unique<VCDInfoObserverImpl>(
      base::BindRepeating(&CameraHalDispatcherImpl::AddCameraIdToDeviceIdEntry,
                          weak_factory_.GetWeakPtr()));

  if (ash::mojo_service_manager::IsServiceManagerBound()) {
    auto* proxy = ash::mojo_service_manager::GetServiceManagerProxy();
    proxy->Register(
        /*service_name=*/chromeos::mojo_services::kCrosCameraHalDispatcher,
        provider_receiver_.BindNewPipeAndPassRemote());
  }
  return true;
}

void CameraHalDispatcherImpl::AddClientObserver(
    CameraClientObserver* observer,
    base::OnceCallback<void(int32_t)> result_callback) {
  // If |proxy_thread_| fails to start in Start() then CameraHalDelegate will
  // not be created, and this function will not be called.
  DCHECK(proxy_thread_.IsRunning());
  base::WaitableEvent added;
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&CameraHalDispatcherImpl::AddClientObserverOnProxyThread,
                     base::Unretained(this), observer,
                     std::move(result_callback), base::Unretained(&added)));
  added.Wait();
}

bool CameraHalDispatcherImpl::IsStarted() {
  return proxy_thread_.IsRunning();
}

void CameraHalDispatcherImpl::AddActiveClientObserver(
    CameraActiveClientObserver* observer) {
  base::AutoLock lock(opened_camera_id_map_lock_);
  for (auto& [camera_client_type, camera_id_set] : opened_camera_id_map_) {
    if (!camera_id_set.empty()) {
      observer->OnActiveClientChange(camera_client_type,
                                     /*is_new_active_client=*/true,
                                     GetDeviceIdsFromCameraIds(camera_id_set));
    }
  }
  active_client_observers_->AddObserver(observer);
}

void CameraHalDispatcherImpl::RemoveActiveClientObserver(
    CameraActiveClientObserver* observer) {
  active_client_observers_->RemoveObserver(observer);
}

base::flat_map<std::string, cros::mojom::CameraPrivacySwitchState>
CameraHalDispatcherImpl::AddCameraPrivacySwitchObserver(
    CameraPrivacySwitchObserver* observer) {
  privacy_switch_observers_->AddObserver(observer);
  base::AutoLock lock(device_id_to_hw_privacy_switch_state_lock_);
  return device_id_to_hw_privacy_switch_state_;
}

void CameraHalDispatcherImpl::RemoveCameraPrivacySwitchObserver(
    CameraPrivacySwitchObserver* observer) {
  privacy_switch_observers_->RemoveObserver(observer);
}

void CameraHalDispatcherImpl::AddCameraEffectObserver(
    CameraEffectObserver* observer) {
  camera_effect_observers_->AddObserver(observer);
}

void CameraHalDispatcherImpl::RemoveCameraEffectObserver(
    CameraEffectObserver* observer) {
  camera_effect_observers_->RemoveObserver(observer);
}

void CameraHalDispatcherImpl::GetCameraSWPrivacySwitchState(
    cros::mojom::CrosCameraService::GetCameraSWPrivacySwitchStateCallback
        callback) {
  if (!proxy_thread_.IsRunning()) {
    LOG(ERROR) << "CameraProxyThread is not started. Failed to query the "
                  "camera SW privacy switch state";
    std::move(callback).Run(cros::mojom::CameraPrivacySwitchState::UNKNOWN);
    return;
  }
  // Unretained reference is safe here because CameraHalDispatcherImpl owns
  // |proxy_thread_|.
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &CameraHalDispatcherImpl::GetCameraSWPrivacySwitchStateOnProxyThread,
          base::Unretained(this), std::move(callback)));
}

void CameraHalDispatcherImpl::SetCameraSWPrivacySwitchState(
    cros::mojom::CameraPrivacySwitchState state) {
  if (!proxy_thread_.IsRunning()) {
    LOG(ERROR) << "CameraProxyThread is not started. "
                  "SetCameraSWPrivacySwitchState request was aborted";
    return;
  }
  // Unretained reference is safe here because CameraHalDispatcherImpl owns
  // |proxy_thread_|.
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &CameraHalDispatcherImpl::SetCameraSWPrivacySwitchStateOnProxyThread,
          base::Unretained(this), state));
}

void CameraHalDispatcherImpl::RegisterPluginVmToken(
    const base::UnguessableToken& token) {
  token_manager_.RegisterPluginVmToken(token);
}

void CameraHalDispatcherImpl::UnregisterPluginVmToken(
    const base::UnguessableToken& token) {
  token_manager_.UnregisterPluginVmToken(token);
}

void CameraHalDispatcherImpl::AddCameraIdToDeviceIdEntry(
    int32_t camera_id,
    const std::string& device_id) {
  base::AutoLock lock(camera_id_to_device_id_lock_);
  camera_id_to_device_id_[camera_id] = device_id;
}

CameraHalDispatcherImpl::CameraHalDispatcherImpl()
    : proxy_thread_("CameraProxyThread"),
      camera_service_observer_receiver_(this),
      active_client_observers_(
          new base::ObserverListThreadSafe<CameraActiveClientObserver>()),
      privacy_switch_observers_(
          new base::ObserverListThreadSafe<CameraPrivacySwitchObserver>()),
      camera_effect_observers_(
          new base::ObserverListThreadSafe<CameraEffectObserver>()) {}

CameraHalDispatcherImpl::~CameraHalDispatcherImpl() {
  VLOG(1) << "Stopping CameraHalDispatcherImpl...";
  if (proxy_thread_.IsRunning()) {
    proxy_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&CameraHalDispatcherImpl::StopOnProxyThread,
                                  base::Unretained(this)));
    proxy_thread_.Stop();
  }
  CAMERA_LOG(EVENT) << "CameraHalDispatcherImpl stopped";
}

void CameraHalDispatcherImpl::RegisterClientWithToken(
    mojo::PendingRemote<cros::mojom::CameraHalClient> client,
    cros::mojom::CameraClientType type,
    const base::UnguessableToken& auth_token,
    RegisterClientWithTokenCallback callback) {
  base::UnguessableToken client_auth_token = auth_token;
  // Unretained reference is safe here because CameraHalDispatcherImpl owns
  // |proxy_thread_|.
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &CameraHalDispatcherImpl::RegisterClientWithTokenOnProxyThread,
          base::Unretained(this), std::move(client), type,
          std::move(client_auth_token),
          base::BindPostTaskToCurrentDefault(std::move(callback))));
}

void CameraHalDispatcherImpl::CameraDeviceActivityChange(
    int32_t camera_id,
    bool opened,
    cros::mojom::CameraClientType type) {
  VLOG(1) << type << (opened ? " opened " : " closed ") << "camera "
          << camera_id;
  base::AutoLock lock(opened_camera_id_map_lock_);
  auto& camera_id_set = opened_camera_id_map_[type];
  if (opened) {
    auto result = camera_id_set.insert(camera_id);
    if (!result.second) {  // No element inserted.
      LOG(WARNING) << "Received duplicated open notification for camera "
                   << camera_id;
      return;
    }
    if (camera_id_set.size() == 1) {
      VLOG(1) << type << " is active";
    }
  } else {
    auto it = camera_id_set.find(camera_id);
    if (it == camera_id_set.end()) {
      // This can happen if something happened to the client process and it
      // simultaneous lost connections to both CameraHalDispatcher and
      // CameraHalServer.
      LOG(WARNING) << "Received close notification for camera " << camera_id
                   << " which is not opened";
      return;
    }
    camera_id_set.erase(it);
    if (camera_id_set.empty()) {
      VLOG(1) << type << " is inactive";
    }
  }
  bool is_new_active_client = camera_id_set.size() == 1 && opened;
  active_client_observers_->Notify(
      FROM_HERE, &CameraActiveClientObserver::OnActiveClientChange, type,
      is_new_active_client, GetDeviceIdsFromCameraIds(camera_id_set));
}

void CameraHalDispatcherImpl::CameraPrivacySwitchStateChange(
    cros::mojom::CameraPrivacySwitchState state,
    int32_t camera_id) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  const std::string& device_id = GetDeviceIdFromCameraId(camera_id);
  base::AutoLock lock(device_id_to_hw_privacy_switch_state_lock_);
  device_id_to_hw_privacy_switch_state_[device_id] = state;
  privacy_switch_observers_->Notify(
      FROM_HERE,
      &CameraPrivacySwitchObserver::OnCameraHWPrivacySwitchStateChanged,
      device_id, state);
  CAMERA_LOG(EVENT) << "Camera privacy switch state changed: " << state;
}

void CameraHalDispatcherImpl::CameraSWPrivacySwitchStateChange(
    cros::mojom::CameraPrivacySwitchState state) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());

  privacy_switch_observers_->Notify(
      FROM_HERE,
      &CameraPrivacySwitchObserver::OnCameraSWPrivacySwitchStateChanged, state);
  CAMERA_LOG(EVENT) << "Camera software privacy switch state changed: "
                    << state;
}

void CameraHalDispatcherImpl::CameraEffectChange(
    cros::mojom::EffectsConfigPtr config) {}
void CameraHalDispatcherImpl::AutoFramingStateChange(
    cros::mojom::CameraAutoFramingState state) {}

base::UnguessableToken CameraHalDispatcherImpl::GetTokenForTrustedClient(
    cros::mojom::CameraClientType type) {
  return token_manager_.GetTokenForTrustedClient(type);
}

void CameraHalDispatcherImpl::GetCameraSWPrivacySwitchStateOnProxyThread(
    cros::mojom::CrosCameraService::GetCameraSWPrivacySwitchStateCallback
        callback) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  if (!camera_service_.is_bound()) {
    LOG(ERROR) << "CameraHalDispatcherImpl has not connected to cros_camera "
                  "service yet.";
    std::move(callback).Run(cros::mojom::CameraPrivacySwitchState::UNKNOWN);
    return;
  }
  camera_service_->GetCameraSWPrivacySwitchState(std::move(callback));
}

void CameraHalDispatcherImpl::SetCameraSWPrivacySwitchStateOnProxyThread(
    cros::mojom::CameraPrivacySwitchState state) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  if (!camera_service_.is_bound()) {
    LOG(ERROR) << "CameraHalDispatcherImpl has not connected to cros_camera "
                  "service yet.";
    return;
  }
  camera_service_->SetCameraSWPrivacySwitchState(state);
}

void CameraHalDispatcherImpl::RegisterClientWithTokenOnProxyThread(
    mojo::PendingRemote<cros::mojom::CameraHalClient> client,
    cros::mojom::CameraClientType type,
    base::UnguessableToken auth_token,
    RegisterClientWithTokenCallback callback) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  auto client_observer = std::make_unique<MojoCameraClientObserver>(
      std::move(client), type, std::move(auth_token));
  client_observer->client().set_disconnect_handler(base::BindOnce(
      &CameraHalDispatcherImpl::OnCameraHalClientConnectionError,
      base::Unretained(this), base::Unretained(client_observer.get())));
  AddClientObserverOnProxyThread(client_observer.get(), std::move(callback),
                                 nullptr);
  mojo_client_observers_[client_observer.get()] = std::move(client_observer);
}

void CameraHalDispatcherImpl::AddClientObserverOnProxyThread(
    CameraClientObserver* observer,
    base::OnceCallback<void(int32_t)> result_callback,
    base::WaitableEvent* added) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  if (!observer->Authenticate(&token_manager_)) {
    LOG(ERROR) << "Failed to authenticate camera client observer";
    std::move(result_callback).Run(-EPERM);
    return;
  }
  if (camera_service_.is_bound()) {
    EstablishMojoChannel(observer);
  }
  // If the cros camera service is stopped, we just put it in the observer list.
  // The mojo channel will be established once the cros camera service starts.
  client_observers_.insert(observer);
  std::move(result_callback).Run(0);
  CAMERA_LOG(EVENT) << "Camera HAL client registered";
  if (added) {
    added->Signal();
  }
}

void CameraHalDispatcherImpl::EstablishMojoChannel(
    CameraClientObserver* client_observer) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  const auto& type = client_observer->GetType();
  CAMERA_LOG(EVENT) << "Establishing server channel for " << type;
  camera_service_->GetCameraModule(
      type,
      base::BindOnce(
          &CameraHalDispatcherImpl::OnGetCameraModule,
          // TODO(b/322727099): client_observer may be a dangling pointer since
          // lifetime of CameraClientObserver is shorter than
          // CameraHalDispatcher. Check the lifetime issue during refactoring.
          base::Unretained(this),
          base::UnsafeDanglingUntriaged(client_observer)));
}

void CameraHalDispatcherImpl::OnGetCameraModule(
    CameraClientObserver* client_observer,
    mojo::PendingRemote<cros::mojom::CameraModule> camera_module) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  if (client_observers_.find(client_observer) == client_observers_.end()) {
    return;
  }
  client_observer->OnChannelCreated(std::move(camera_module));
}

void CameraHalDispatcherImpl::OnPeerConnected(
    mojo::ScopedMessagePipeHandle message_pipe) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  receiver_set_.Add(this,
                    mojo::PendingReceiver<cros::mojom::CameraHalDispatcher>(
                        std::move(message_pipe)));
  VLOG(1) << "New CameraHalDispatcher binding added";
}

void CameraHalDispatcherImpl::OnCameraServiceConnectionError() {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  {
    base::AutoLock lock(opened_camera_id_map_lock_);
    CAMERA_LOG(EVENT) << "Camera HAL server connection lost";
    camera_service_.reset();
    camera_service_observer_receiver_.reset();
    for (auto& [camera_client_type, camera_id_set] : opened_camera_id_map_) {
      if (!camera_id_set.empty()) {
        active_client_observers_->Notify(
            FROM_HERE, &CameraActiveClientObserver::OnActiveClientChange,
            camera_client_type, /*is_new_active_client=*/false,
            /*active_device_ids=*/base::flat_set<std::string>());
      }
    }
    opened_camera_id_map_.clear();
  }

  {
    base::AutoLock lock(device_id_to_hw_privacy_switch_state_lock_);
    device_id_to_hw_privacy_switch_state_.clear();
  }
  privacy_switch_observers_->Notify(
      FROM_HERE,
      &CameraPrivacySwitchObserver::OnCameraHWPrivacySwitchStateChanged,
      std::string(), cros::mojom::CameraPrivacySwitchState::UNKNOWN);
}

void CameraHalDispatcherImpl::OnCameraHalClientConnectionError(
    CameraClientObserver* client_observer) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  CleanupClientOnProxyThread(client_observer);
}

void CameraHalDispatcherImpl::CleanupClientOnProxyThread(
    CameraClientObserver* client_observer) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  base::AutoLock lock(opened_camera_id_map_lock_);
  auto camera_client_type = client_observer->GetType();
  auto opened_it = opened_camera_id_map_.find(camera_client_type);
  if (opened_it != opened_camera_id_map_.end()) {
    const auto& camera_id_set = opened_it->second;
    if (!camera_id_set.empty()) {
      active_client_observers_->Notify(
          FROM_HERE, &CameraActiveClientObserver::OnActiveClientChange,
          camera_client_type,
          /*is_new_active_client=*/false,
          /*active_device_ids=*/base::flat_set<std::string>());
    }
    opened_camera_id_map_.erase(opened_it);
  }

  if (mojo_client_observers_.find(client_observer) !=
      mojo_client_observers_.end()) {
    mojo_client_observers_[client_observer].reset();
    mojo_client_observers_.erase(client_observer);
  }

  auto it = client_observers_.find(client_observer);
  if (it != client_observers_.end()) {
    client_observers_.erase(it);
    CAMERA_LOG(EVENT) << "Camera HAL client connection lost";
  }
}

void CameraHalDispatcherImpl::RemoveClientObserversOnProxyThread(
    std::vector<CameraClientObserver*> client_observers,
    base::WaitableEvent* removed) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  for (auto* client_observer : client_observers) {
    CleanupClientOnProxyThread(client_observer);
  }
  removed->Signal();
}

void CameraHalDispatcherImpl::RemoveClientObservers(
    std::vector<CameraClientObserver*> client_observers) {
  if (client_observers.empty()) {
    return;
  }
  DCHECK(proxy_thread_.IsRunning());
  base::WaitableEvent removed;
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &CameraHalDispatcherImpl::RemoveClientObserversOnProxyThread,
          base::Unretained(this), client_observers,
          base::Unretained(&removed)));
  removed.Wait();
}

void CameraHalDispatcherImpl::StopOnProxyThread() {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());

  mojo_client_observers_.clear();
  client_observers_.clear();
  camera_service_observer_receiver_.reset();
  camera_service_.reset();
  receiver_set_.Clear();
  {
    base::AutoLock lock(device_id_to_hw_privacy_switch_state_lock_);
    device_id_to_hw_privacy_switch_state_.clear();
  }
}

void CameraHalDispatcherImpl::SetAutoFramingState(
    cros::mojom::CameraAutoFramingState state) {
  if (!proxy_thread_.IsRunning()) {
    // The camera hal dispatcher is not running, ignore the request.
    // TODO(pihsun): Any better way?
    return;
  }
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&CameraHalDispatcherImpl::SetAutoFramingStateOnProxyThread,
                     base::Unretained(this), state));
}

void CameraHalDispatcherImpl::SetAutoFramingStateOnProxyThread(
    cros::mojom::CameraAutoFramingState state) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());

  current_auto_framing_state_ = state;
  if (camera_service_.is_bound()) {
    camera_service_->SetAutoFramingState(state);
  }
}

void CameraHalDispatcherImpl::GetAutoFramingSupported(
    cros::mojom::CrosCameraService::GetAutoFramingSupportedCallback callback) {
  if (!proxy_thread_.IsRunning()) {
    std::move(callback).Run(false);
    return;
  }
  // Unretained reference is safe here because CameraHalDispatcherImpl owns
  // |proxy_thread_|.
  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &CameraHalDispatcherImpl::GetAutoFramingSupportedOnProxyThread,
          base::Unretained(this),
          // Make sure to hop back to the current thread for the reply.
          base::BindPostTaskToCurrentDefault(std::move(callback), FROM_HERE)));
}

void CameraHalDispatcherImpl::GetAutoFramingSupportedOnProxyThread(
    cros::mojom::CrosCameraService::GetAutoFramingSupportedCallback callback) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
  if (!camera_service_.is_bound()) {
    // TODO(pihsun): Currently only AutozoomControllerImpl calls
    // GetAutoFramingSupported. Support multiple call to the function using
    // CallbackList if it's needed.
    DCHECK(!auto_framing_supported_callback_);
    auto_framing_supported_callback_ = std::move(callback);
    return;
  }
  camera_service_->GetAutoFramingSupported(std::move(callback));
}

void CameraHalDispatcherImpl::SetCameraEffects(
    cros::mojom::EffectsConfigPtr config) {
  if (!proxy_thread_.IsRunning()) {
    LOG(ERROR) << "CameraHalDispatcherImpl Error: calling SetCameraEffects "
                  "without proxy_thread_ running.";
    // The camera hal dispatcher is not running, ignore the request.
    // Notify with nullopt as the proxy thread is not running and camera effects
    // cannot be set in this case.
    camera_effect_observers_->Notify(
        FROM_HERE, &CameraEffectObserver::OnCameraEffectChanged,
        cros::mojom::EffectsConfigPtr());
    return;
  }

  proxy_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&CameraHalDispatcherImpl::SetCameraEffectsOnProxyThread,
                     base::Unretained(this), std::move(config),
                     /*is_from_register=*/false));
}

void CameraHalDispatcherImpl::SetCameraEffectsOnProxyThread(
    cros::mojom::EffectsConfigPtr config,
    bool is_from_register) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());

  if (camera_service_.is_bound()) {
    camera_service_->SetCameraEffect(
        config.Clone(),
        base::BindOnce(
            &CameraHalDispatcherImpl::OnSetCameraEffectsCompleteOnProxyThread,
            base::Unretained(this), config.Clone(), is_from_register));

  } else {
    // Save the config to initial_effects_ so that it will be applied when the
    // server becomes ready.
    initial_effects_ = std::move(config);

    LOG(ERROR)
        << "CameraHalDispatcherImpl Error: calling "
           "SetCameraEffectsOnProxyThread without camera server registered.";
    // Notify with nullopt as no camera server has been registered and camera
    // effects cannot be set in this case.
    camera_effect_observers_->Notify(
        FROM_HERE, &CameraEffectObserver::OnCameraEffectChanged,
        cros::mojom::EffectsConfigPtr());
  }
}

void CameraHalDispatcherImpl::OnSetCameraEffectsCompleteOnProxyThread(
    cros::mojom::EffectsConfigPtr config,
    bool is_from_register,
    cros::mojom::SetEffectResult result) {
  DCHECK(proxy_task_runner_->BelongsToCurrentThread());

  cros::mojom::EffectsConfigPtr new_effects;
  // The new config is applied if set effects succeed. If the set effects fail,
  // no effects have been applied if the set is called from register and
  // the current effects do not change otherwise.
  //
  // The new config is applied if set effects succeed.
  if (result == cros::mojom::SetEffectResult::kOk) {
    new_effects = config.Clone();
  }
  // New config is not applied if set effects failed.
  else {
    LOG(ERROR) << "CameraHalDispatcherImpl Error: SetCameraEffectsComplete "
                  "returns with error code "
               << static_cast<int>(result);

    // If setting from register and failed, the new effects should be the
    // default effects.
    if (is_from_register) {
      new_effects = cros::mojom::EffectsConfig::New();
    }
    // If not setting from register, the new effects should still be the current
    // effects.
    else {
      new_effects = current_effects_.Clone();
    }
  }

  // Record the up-to-date camera effects.
  current_effects_ = new_effects.Clone();
  // Reset the `initial_effects_` if the `current_effects_` is not null.
  if (!current_effects_.is_null()) {
    initial_effects_.reset();
  }

  // Notify the camera effect configuration changes with the new effect.
  camera_effect_observers_->Notify(FROM_HERE,
                                   &CameraEffectObserver::OnCameraEffectChanged,
                                   std::move(new_effects));
}

std::string CameraHalDispatcherImpl::GetDeviceIdFromCameraId(
    int32_t camera_id) {
  base::AutoLock lock(camera_id_to_device_id_lock_);
  auto it = camera_id_to_device_id_.find(camera_id);
  if (it == camera_id_to_device_id_.end()) {
    LOG(ERROR) << "Could not find device_id corresponding to camera_id: "
               << camera_id;
    return std::string();
  }
  return it->second;
}

base::flat_set<std::string> CameraHalDispatcherImpl::GetDeviceIdsFromCameraIds(
    base::flat_set<int32_t> camera_ids) {
  base::flat_set<std::string> device_ids;
  for (const auto& camera_id : camera_ids) {
    device_ids.insert(GetDeviceIdFromCameraId(camera_id));
  }
  return device_ids;
}

TokenManager* CameraHalDispatcherImpl::GetTokenManagerForTesting() {
  return &token_manager_;
}

void CameraHalDispatcherImpl::Request(
    chromeos::mojo_service_manager::mojom::ProcessIdentityPtr identity,
    mojo::ScopedMessagePipeHandle receiver) {
  // Unretained reference is safe here because CameraHalDispatcherImpl owns
  // |proxy_thread_|.
  proxy_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&CameraHalDispatcherImpl::OnPeerConnected,
                                base::Unretained(this), std::move(receiver)));
  VLOG(1) << "New CameraHalDispatcher binding added from Mojo Service Manager.";
}

}  // namespace media