blob: 1cc01c7dc8aff361f646b37077b6d5f57e7bb827 [file] [log] [blame]
/* Copyright 2022 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "hal/fake/camera_hal.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <base/containers/contains.h>
#include <base/no_destructor.h>
#include <base/strings/string_number_conversions.h>
#include <base/task/sequenced_task_runner.h>
#include "cros-camera/common.h"
#include "cros-camera/cros_camera_hal.h"
#include "hal/fake/metadata_handler.h"
namespace cros {
namespace {
// The default fake hal spec file. The file should contain a JSON that is
// parsed to HalSpec struct.
constexpr const char kDefaultFakeHalSpecFile[] = "/etc/camera/fake_hal.json";
constexpr const char kOverrideFakeHalSpecFile[] = "/run/camera/fake_hal.json";
// This is the sleep time (0.3s) between reporting disconnect and connect event
// to upper layer, to avoid race condition in Chrome.
// TODO(b:261682032): Check if the time can be lower, and/or fix the issue in
// Chrome so this is not needed.
constexpr const base::TimeDelta kReconnectDelay = base::Milliseconds(300);
int camera_device_open_ext(const hw_module_t* module,
const char* name,
hw_device_t** device,
ClientType client_type) {
// Make sure hal adapter loads the correct symbol.
if (module != &HAL_MODULE_INFO_SYM.common) {
LOGF(ERROR) << "Invalid module " << module << " expected "
<< &HAL_MODULE_INFO_SYM.common;
return -EINVAL;
}
int id;
if (!base::StringToInt(name, &id)) {
LOGF(ERROR) << "Invalid camera name " << name;
return -EINVAL;
}
return CameraHal::GetInstance().OpenDevice(id, module, device, client_type);
}
int get_camera_info_ext(int id,
struct camera_info* info,
ClientType client_type) {
return CameraHal::GetInstance().GetCameraInfo(id, info, client_type);
}
int camera_device_open(const hw_module_t* module,
const char* name,
hw_device_t** device) {
return camera_device_open_ext(module, name, device, ClientType::kChrome);
}
int get_number_of_cameras() {
return CameraHal::GetInstance().GetNumberOfCameras();
}
int get_camera_info(int id, struct camera_info* info) {
return get_camera_info_ext(id, info, ClientType::kChrome);
}
int set_callbacks(const camera_module_callbacks_t* callbacks) {
return CameraHal::GetInstance().SetCallbacks(callbacks);
}
int init() {
return CameraHal::GetInstance().Init();
}
void set_up(CameraMojoChannelManagerToken* token) {
CameraHal::GetInstance().SetUp(token);
}
void tear_down() {
CameraHal::GetInstance().TearDown();
}
void set_privacy_switch_callback(PrivacySwitchStateChangeCallback callback) {
CameraHal::GetInstance().SetPrivacySwitchCallback(callback);
}
int open_legacy(const struct hw_module_t* module,
const char* id,
uint32_t halVersion,
struct hw_device_t** device) {
return -ENOSYS;
}
int set_torch_mode(const char* camera_id, bool enabled) {
return -ENOSYS;
}
} // namespace
CameraHal::CameraHal() {
// The constructor is first called by set_up which is not on the same
// sequence as the other methods this class is run on.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
CameraHal::~CameraHal() = default;
CameraHal& CameraHal::GetInstance() {
// Leak the static camera HAL here, since it has a non-trivial destructor
// (from ReloadableConfigFile -> base::FilePathWatcher).
static base::NoDestructor<CameraHal> camera_hal;
return *camera_hal;
}
int CameraHal::GetNumberOfCameras() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return 0;
}
int CameraHal::SetCallbacks(const camera_module_callbacks_t* callbacks) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
callbacks_ = callbacks;
ApplySpec();
return 0;
}
int CameraHal::Init() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
config_file_ = std::make_unique<ReloadableConfigFile>(
ReloadableConfigFile::Options{base::FilePath(kDefaultFakeHalSpecFile),
base::FilePath(kOverrideFakeHalSpecFile)});
config_file_->SetCallback(
base::BindRepeating(&CameraHal::OnSpecUpdated, base::Unretained(this)));
return 0;
}
void CameraHal::SetUp(CameraMojoChannelManagerToken* token) {
mojo_manager_token_ = token;
}
void CameraHal::TearDown() {}
void CameraHal::SetPrivacySwitchCallback(
PrivacySwitchStateChangeCallback callback) {}
int CameraHal::OpenDevice(int id,
const hw_module_t* module,
hw_device_t** hw_device,
ClientType client_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOGFID(1, id);
if (!IsCameraIdValid(id)) {
LOGF(ERROR) << "Camera ID " << id << " is invalid";
return -ENODEV;
}
auto it = std::find_if(hal_spec_.cameras.begin(), hal_spec_.cameras.end(),
[id](const auto& spec) { return spec.id == id; });
CHECK(it != hal_spec_.cameras.end());
cameras_[id] = std::make_unique<CameraClient>(
id, static_metadata_[id], request_template_[id], module, hw_device, *it);
int ret = cameras_[id]->OpenDevice();
if (ret != 0) {
cameras_.erase(id);
return -ENODEV;
}
return 0;
}
bool CameraHal::IsCameraIdValid(int id) {
return base::Contains(hal_spec_.cameras, id,
[](const auto& spec) { return spec.id; });
}
int CameraHal::GetCameraInfo(int id,
struct camera_info* info,
ClientType client_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOGFID(1, id);
if (!IsCameraIdValid(id)) {
LOGF(ERROR) << "Camera ID " << id << " is invalid";
return -EINVAL;
}
info->facing = CAMERA_FACING_EXTERNAL;
info->orientation = 0;
info->device_version = CAMERA_DEVICE_API_VERSION_3_5;
info->static_camera_characteristics = static_metadata_[id].getAndLock();
info->resource_cost = 0;
info->conflicting_devices = nullptr;
info->conflicting_devices_length = 0;
return 0;
}
void CameraHal::OnSpecUpdated(const base::Value::Dict& json_values) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto hal_spec = ParseHalSpecFromJsonValue(json_values);
if (!hal_spec.has_value()) {
LOGF(WARNING) << "config file is not formatted correctly, ignored.";
return;
}
hal_spec_ = hal_spec.value();
ApplySpec();
}
bool CameraHal::SetUpCamera(int id, const CameraSpec& spec) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
android::CameraMetadata static_metadata, request_template;
if (!FillDefaultMetadata(&static_metadata, &request_template, spec)) {
return false;
}
static_metadata_[id] = std::move(static_metadata);
request_template_[id] = std::move(request_template);
return true;
}
void CameraHal::TearDownCamera(int id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static_metadata_.erase(id);
request_template_.erase(id);
}
void CameraHal::NotifyCameraConnected(int id, bool connected) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callbacks_ != nullptr);
callbacks_->camera_device_status_change(
callbacks_, id,
connected ? CAMERA_DEVICE_STATUS_PRESENT
: CAMERA_DEVICE_STATUS_NOT_PRESENT);
}
void CameraHal::ApplySpec() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (callbacks_ == nullptr) {
// Spec will be applied when callback is set.
return;
}
bool should_apply_delayed_spec = false;
auto new_applied_spec = hal_spec_;
for (const auto& old_camera_spec : applied_hal_spec_.cameras) {
int id = old_camera_spec.id;
auto it = std::find_if(new_applied_spec.cameras.begin(),
new_applied_spec.cameras.end(),
[&](const auto& spec) { return spec.id == id; });
// TODO(pihsun): Might need to close the camera if some currently opened
// camera is removed in spec.
if (it == new_applied_spec.cameras.end()) {
// Camera entry removed.
if (old_camera_spec.connected) {
VLOGF(1) << "Removing camera " << id;
NotifyCameraConnected(id, false);
TearDownCamera(id);
}
} else {
if (it->connected != old_camera_spec.connected) {
// Device connected state changed.
VLOGF(1) << "Camera " << id << " connected state changed "
<< old_camera_spec.connected << "->" << it->connected;
NotifyCameraConnected(id, it->connected);
} else if (it->connected) {
bool should_reconnect = false;
if (it->supported_formats != old_camera_spec.supported_formats) {
VLOGF(1) << "Camera " << id << " supported formats changed";
NotifyCameraConnected(id, false);
TearDownCamera(id);
// Supported format changes change static metadata, so we need to
// regenerate static metadata here.
if (!SetUpCamera(id, *it)) {
// TODO(pihsun): Better handle error? We should at least remove the
// entry from the new spec.
LOGF(WARNING) << "Error when setting up camera id " << id;
continue;
}
should_reconnect = true;
} else if (it->frames != old_camera_spec.frames) {
// TODO(pihsun): For frames spec change it's possible to just start
// returning new frames in the CameraClient instead of simulating
// unplug / plug the camera.
VLOGF(1) << "Camera " << id << " frames spec changed";
NotifyCameraConnected(id, false);
should_reconnect = true;
}
if (should_reconnect) {
should_apply_delayed_spec = true;
it->connected = false;
}
}
}
}
for (const auto& camera_spec : new_applied_spec.cameras) {
int id = camera_spec.id;
if (std::none_of(applied_hal_spec_.cameras.begin(),
applied_hal_spec_.cameras.end(),
[&](const auto& spec) { return spec.id == id; })) {
// New device.
VLOGF(1) << "Adding camera " << id;
if (!SetUpCamera(id, camera_spec)) {
// TODO(pihsun): Better handle error? We should at least remove the
// entry from the new spec.
LOGF(WARNING) << "Error when setting up camera id " << id;
continue;
}
NotifyCameraConnected(id, camera_spec.connected);
}
}
applied_hal_spec_ = new_applied_spec;
// TODO(b:261682032): This is for workaround that fake HAL plugging /
// unplugging camera too quickly will cause issue in Chrome.
if (should_apply_delayed_spec) {
apply_spec_timer_.Start(FROM_HERE, kReconnectDelay, this,
&CameraHal::ApplySpec);
}
}
void CameraHal::CloseDevice(int id) {
VLOGFID(1, id);
DCHECK(task_runner_);
// The CameraHal is Singleton with NoDestructor so base::Unretained(this)
// will always be valid.
// Most of the work, like stopping the request handler thread, is done in
// CameraClient::CloseDevice, and this only removes the CameraClient from the
// CameraHal.
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&CameraHal::CloseDeviceOnHalThread,
base::Unretained(this), id));
}
void CameraHal::CloseDeviceOnHalThread(int id) {
VLOGFID(1, id);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cameras_.erase(id);
}
int camera_device_close(struct hw_device_t* hw_device) {
camera3_device_t* cam_dev = reinterpret_cast<camera3_device_t*>(hw_device);
CameraClient* cam = static_cast<CameraClient*>(cam_dev->priv);
if (!cam) {
LOGF(ERROR) << "Camera device is NULL";
return -EIO;
}
cam_dev->priv = nullptr;
int ret = cam->CloseDevice();
CameraHal::GetInstance().CloseDevice(cam->GetId());
return ret;
}
} // namespace cros
static hw_module_methods_t gCameraModuleMethods = {
.open = cros::camera_device_open};
camera_module_t HAL_MODULE_INFO_SYM CROS_CAMERA_EXPORT = {
.common = {.tag = HARDWARE_MODULE_TAG,
.module_api_version = CAMERA_MODULE_API_VERSION_2_4,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = CAMERA_HARDWARE_MODULE_ID,
// TODO(pihsun): Extract the module name to a constant if more
// tests needs to have special case for fake HAL.
.name = "Fake Camera HAL",
.author = "The ChromiumOS Authors",
.methods = &gCameraModuleMethods,
.dso = nullptr,
.reserved = {}},
.get_number_of_cameras = cros::get_number_of_cameras,
.get_camera_info = cros::get_camera_info,
.set_callbacks = cros::set_callbacks,
// TODO(pihsun): Implement faking vendor tags.
.get_vendor_tag_ops = nullptr,
.open_legacy = cros::open_legacy,
.set_torch_mode = cros::set_torch_mode,
.init = cros::init,
.reserved = {}};
cros::cros_camera_hal_t CROS_CAMERA_HAL_INFO_SYM CROS_CAMERA_EXPORT = {
.set_up = cros::set_up,
.tear_down = cros::tear_down,
.set_privacy_switch_callback = cros::set_privacy_switch_callback,
.camera_device_open_ext = cros::camera_device_open_ext,
.get_camera_info_ext = cros::get_camera_info_ext};