/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2021, Google Inc.
 *
 * camera_capabilities.cpp - Camera static properties manager
 */

#include "camera_capabilities.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <map>
#include <type_traits>

#include <hardware/camera3.h>

#include <libcamera/base/log.h>

#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/property_ids.h>

#include "libcamera/internal/formats.h"

using namespace libcamera;

LOG_DECLARE_CATEGORY(HAL)

namespace {

/*
 * \var camera3Resolutions
 * \brief The list of image resolutions defined as mandatory to be supported by
 * the Android Camera3 specification
 */
const std::vector<Size> camera3Resolutions = {
	{ 320, 240 },
	{ 640, 480 },
	{ 1280, 720 },
	{ 1920, 1080 }
};

/*
 * \struct Camera3Format
 * \brief Data associated with an Android format identifier
 * \var libcameraFormats List of libcamera pixel formats compatible with the
 * Android format
 * \var name The human-readable representation of the Android format code
 */
struct Camera3Format {
	std::vector<PixelFormat> libcameraFormats;
	bool mandatory;
	const char *name;
};

/*
 * \var camera3FormatsMap
 * \brief Associate Android format code with ancillary data
 */
const std::map<int, const Camera3Format> camera3FormatsMap = {
	{
		HAL_PIXEL_FORMAT_BLOB, {
			{ formats::MJPEG },
			true,
			"BLOB"
		}
	}, {
		HAL_PIXEL_FORMAT_YCbCr_420_888, {
			{ formats::NV12, formats::NV21 },
			true,
			"YCbCr_420_888"
		}
	}, {
		/*
		 * \todo Translate IMPLEMENTATION_DEFINED inspecting the gralloc
		 * usage flag. For now, copy the YCbCr_420 configuration.
		 */
		HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, {
			{ formats::NV12, formats::NV21 },
			true,
			"IMPLEMENTATION_DEFINED"
		}
	}, {
		HAL_PIXEL_FORMAT_RAW10, {
			{
				formats::SBGGR10_CSI2P,
				formats::SGBRG10_CSI2P,
				formats::SGRBG10_CSI2P,
				formats::SRGGB10_CSI2P
			},
			false,
			"RAW10"
		}
	}, {
		HAL_PIXEL_FORMAT_RAW12, {
			{
				formats::SBGGR12_CSI2P,
				formats::SGBRG12_CSI2P,
				formats::SGRBG12_CSI2P,
				formats::SRGGB12_CSI2P
			},
			false,
			"RAW12"
		}
	}, {
		HAL_PIXEL_FORMAT_RAW16, {
			{
				formats::SBGGR16,
				formats::SGBRG16,
				formats::SGRBG16,
				formats::SRGGB16
			},
			false,
			"RAW16"
		}
	},
};

const std::map<camera_metadata_enum_android_info_supported_hardware_level, std::string>
hwLevelStrings = {
	{ ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,  "LIMITED" },
	{ ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_FULL,     "FULL" },
	{ ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,   "LEGACY" },
	{ ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_3,        "LEVEL_3" },
	{ ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, "EXTERNAL" },
};

enum class ControlRange {
	Min,
	Def,
	Max,
};

/**
 * \brief Set Android metadata from libcamera ControlInfo or a default value
 * \tparam T Type of the control in libcamera
 * \tparam U Type of the metadata in Android
 * \param[in] metadata Android metadata pack to add the control value to
 * \param[in] tag Android metadata tag
 * \param[in] controlsInfo libcamera ControlInfoMap from which to find the control info
 * \param[in] control libcamera ControlId to find from \a controlsInfo
 * \param[in] controlRange Whether to use the min, def, or max value from the control info
 * \param[in] defaultValue The value to set in \a metadata if \a control is not found
 *
 * Set the Android metadata entry in \a metadata with tag \a tag based on the
 * control info found for the libcamera control \a control in the libcamera
 * ControlInfoMap \a controlsInfo. If no libcamera ControlInfo is found, then
 * the Android metadata entry is set to \a defaultValue.
 *
 * This function is for scalar values.
 */
template<typename T, typename U>
U setMetadata(CameraMetadata *metadata, uint32_t tag,
	      const ControlInfoMap &controlsInfo, const Control<T> &control,
	      enum ControlRange controlRange, const U defaultValue)
{
	U value = defaultValue;

	const auto &info = controlsInfo.find(&control);
	if (info != controlsInfo.end()) {
		switch (controlRange) {
		case ControlRange::Min:
			value = static_cast<U>(info->second.min().template get<T>());
			break;
		case ControlRange::Def:
			value = static_cast<U>(info->second.def().template get<T>());
			break;
		case ControlRange::Max:
			value = static_cast<U>(info->second.max().template get<T>());
			break;
		}
	}

	metadata->addEntry(tag, value);
	return value;
}

/**
 * \brief Set Android metadata from libcamera ControlInfo or a default value
 * \tparam T Type of the control in libcamera
 * \tparam U Type of the metadata in Android
 * \param[in] metadata Android metadata pack to add the control value to
 * \param[in] tag Android metadata tag
 * \param[in] controlsInfo libcamera ControlInfoMap from which to find the control info
 * \param[in] control libcamera ControlId to find from \a controlsInfo
 * \param[in] defaultVector The value to set in \a metadata if \a control is not found
 *
 * Set the Android metadata entry in \a metadata with tag \a tag based on the
 * control info found for the libcamera control \a control in the libcamera
 * ControlInfoMap \a controlsInfo. If no libcamera ControlInfo is found, then
 * the Android metadata entry is set to \a defaultVector.
 *
 * This function is for vector values.
 */
template<typename T, typename U>
std::vector<U> setMetadata(CameraMetadata *metadata, uint32_t tag,
			   const ControlInfoMap &controlsInfo,
			   const Control<T> &control,
			   const std::vector<U> &defaultVector)
{
	const auto &info = controlsInfo.find(&control);
	if (info == controlsInfo.end()) {
		metadata->addEntry(tag, defaultVector);
		return defaultVector;
	}

	std::vector<U> values(info->second.values().size());
	for (const auto &value : info->second.values())
		values.push_back(static_cast<U>(value.template get<T>()));
	metadata->addEntry(tag, values);

	return values;
}

} /* namespace */

bool CameraCapabilities::validateManualSensorCapability()
{
	const char *noMode = "Manual sensor capability unavailable: ";

	if (!staticMetadata_->entryContains<uint8_t>(ANDROID_CONTROL_AE_AVAILABLE_MODES,
						     ANDROID_CONTROL_AE_MODE_OFF)) {
		LOG(HAL, Info) << noMode << "missing AE mode off";
		return false;
	}

	if (!staticMetadata_->entryContains<uint8_t>(ANDROID_CONTROL_AE_LOCK_AVAILABLE,
						     ANDROID_CONTROL_AE_LOCK_AVAILABLE_TRUE)) {
		LOG(HAL, Info) << noMode << "missing AE lock";
		return false;
	}

	/*
	 * \todo Return true here after we satisfy all the requirements:
	 * https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR
	 * Manual frame duration control
	 *     android.sensor.frameDuration
	 *     android.sensor.info.maxFrameDuration
	 * Manual exposure control
	 *     android.sensor.exposureTime
	 *     android.sensor.info.exposureTimeRange
	 * Manual sensitivity control
	 *     android.sensor.sensitivity
	 *     android.sensor.info.sensitivityRange
	 * Manual lens control (if the lens is adjustable)
	 *     android.lens.*
	 * Manual flash control (if a flash unit is present)
	 *     android.flash.*
	 * Manual black level locking
	 *     android.blackLevel.lock
	 * Auto exposure lock
	 *     android.control.aeLock
	 */
	return false;
}

bool CameraCapabilities::validateManualPostProcessingCapability()
{
	const char *noMode = "Manual post processing capability unavailable: ";

	if (!staticMetadata_->entryContains<uint8_t>(ANDROID_CONTROL_AWB_AVAILABLE_MODES,
						     ANDROID_CONTROL_AWB_MODE_OFF)) {
		LOG(HAL, Info) << noMode << "missing AWB mode off";
		return false;
	}

	if (!staticMetadata_->entryContains<uint8_t>(ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
						     ANDROID_CONTROL_AWB_LOCK_AVAILABLE_TRUE)) {
		LOG(HAL, Info) << noMode << "missing AWB lock";
		return false;
	}

	/*
	 * \todo return true here after we satisfy all the requirements:
	 * https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING
	 * Manual tonemap control
	 *     android.tonemap.curve
	 *     android.tonemap.mode
	 *     android.tonemap.maxCurvePoints
	 *     android.tonemap.gamma
	 *     android.tonemap.presetCurve
	 * Manual white balance control
	 *     android.colorCorrection.transform
	 *     android.colorCorrection.gains
	 * Manual lens shading map control
	 *     android.shading.mode
	 *     android.statistics.lensShadingMapMode
	 *     android.statistics.lensShadingMap
	 *     android.lens.info.shadingMapSize
	 * Manual aberration correction control (if aberration correction is supported)
	 *     android.colorCorrection.aberrationMode
	 *     android.colorCorrection.availableAberrationModes
	 * Auto white balance lock
	 *     android.control.awbLock
	 */
	return false;
}

bool CameraCapabilities::validateBurstCaptureCapability()
{
	camera_metadata_ro_entry_t entry;
	bool found;

	const char *noMode = "Burst capture capability unavailable: ";

	if (!staticMetadata_->entryContains<uint8_t>(ANDROID_CONTROL_AE_LOCK_AVAILABLE,
						     ANDROID_CONTROL_AE_LOCK_AVAILABLE_TRUE)) {
		LOG(HAL, Info) << noMode << "missing AE lock";
		return false;
	}

	if (!staticMetadata_->entryContains<uint8_t>(ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
						     ANDROID_CONTROL_AWB_LOCK_AVAILABLE_TRUE)) {
		LOG(HAL, Info) << noMode << "missing AWB lock";
		return false;
	}

	found = staticMetadata_->getEntry(ANDROID_SYNC_MAX_LATENCY, &entry);
	if (!found || *entry.data.i32 < 0 || 4 < *entry.data.i32) {
		LOG(HAL, Info)
			<< noMode << "max sync latency is "
			<< (found ? std::to_string(*entry.data.i32) : "not present");
		return false;
	}

	/*
	 * \todo return true here after we satisfy all the requirements
	 * https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
	 */
	return false;
}

std::set<camera_metadata_enum_android_request_available_capabilities>
CameraCapabilities::computeCapabilities()
{
	std::set<camera_metadata_enum_android_request_available_capabilities>
		capabilities;

	capabilities.insert(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE);

	if (validateManualSensorCapability())
		capabilities.insert(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);

	if (validateManualPostProcessingCapability())
		capabilities.insert(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING);

	if (validateBurstCaptureCapability())
		capabilities.insert(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE);

	if (rawStreamAvailable_)
		capabilities.insert(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_RAW);

	return capabilities;
}

void CameraCapabilities::computeHwLevel(
	const std::set<camera_metadata_enum_android_request_available_capabilities> &caps)
{
	camera_metadata_ro_entry_t entry;
	bool found;
	camera_metadata_enum_android_info_supported_hardware_level
		hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_FULL;

	if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR))
		hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;

	if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING))
		hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;

	if (!caps.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE))
		hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;

	found = staticMetadata_->getEntry(ANDROID_SYNC_MAX_LATENCY, &entry);
	if (!found || *entry.data.i32 != 0)
		hwLevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;

	hwLevel_ = hwLevel;
}

int CameraCapabilities::initialize(std::shared_ptr<Camera> camera,
				   int orientation, int facing)
{
	camera_ = camera;
	orientation_ = orientation;
	facing_ = facing;
	rawStreamAvailable_ = false;
	maxFrameDuration_ = 0;

	/* Acquire the camera and initialize available stream configurations. */
	int ret = camera_->acquire();
	if (ret) {
		LOG(HAL, Error) << "Failed to temporarily acquire the camera";
		return ret;
	}

	ret = initializeStreamConfigurations();
	if (ret) {
		camera_->release();
		return ret;
	}

	ret = initializeStaticMetadata();
	camera_->release();
	return ret;
}

std::vector<Size>
CameraCapabilities::initializeYUVResolutions(const PixelFormat &pixelFormat,
					     const std::vector<Size> &resolutions)
{
	std::vector<Size> supportedResolutions;
	std::unique_ptr<CameraConfiguration> cameraConfig =
		camera_->generateConfiguration({ StreamRole::Viewfinder });
	if (!cameraConfig) {
		LOG(HAL, Error) << "Failed to get supported YUV resolutions";
		return supportedResolutions;
	}

	StreamConfiguration &cfg = cameraConfig->at(0);

	for (const Size &res : resolutions) {
		cfg.pixelFormat = pixelFormat;
		cfg.size = res;

		CameraConfiguration::Status status = cameraConfig->validate();
		if (status != CameraConfiguration::Valid) {
			LOG(HAL, Debug) << cfg.toString() << " not supported";
			continue;
		}

		LOG(HAL, Debug) << cfg.toString() << " supported";

		supportedResolutions.push_back(res);
	}

	return supportedResolutions;
}

std::vector<Size>
CameraCapabilities::initializeRawResolutions(const PixelFormat &pixelFormat)
{
	std::vector<Size> supportedResolutions;
	std::unique_ptr<CameraConfiguration> cameraConfig =
		camera_->generateConfiguration({ StreamRole::Raw });
	if (!cameraConfig) {
		LOG(HAL, Error) << "Failed to get supported Raw resolutions";
		return supportedResolutions;
	}

	StreamConfiguration &cfg = cameraConfig->at(0);
	const StreamFormats &formats = cfg.formats();
	supportedResolutions = formats.sizes(pixelFormat);

	return supportedResolutions;
}

/*
 * Initialize the format conversion map to translate from Android format
 * identifier to libcamera pixel formats and fill in the list of supported
 * stream configurations to be reported to the Android camera framework through
 * the camera static metadata.
 */
int CameraCapabilities::initializeStreamConfigurations()
{
	/*
	 * Get the maximum output resolutions
	 * \todo Get this from the camera properties once defined
	 */
	std::unique_ptr<CameraConfiguration> cameraConfig =
		camera_->generateConfiguration({ StillCapture });
	if (!cameraConfig) {
		LOG(HAL, Error) << "Failed to get maximum resolution";
		return -EINVAL;
	}
	StreamConfiguration &cfg = cameraConfig->at(0);

	/*
	 * \todo JPEG - Adjust the maximum available resolution by taking the
	 * JPEG encoder requirements into account (alignment and aspect ratio).
	 */
	const Size maxRes = cfg.size;
	LOG(HAL, Debug) << "Maximum supported resolution: " << maxRes.toString();

	/*
	 * Build the list of supported image resolutions.
	 *
	 * The resolutions listed in camera3Resolution are mandatory to be
	 * supported, up to the camera maximum resolution.
	 *
	 * Augment the list by adding resolutions calculated from the camera
	 * maximum one.
	 */
	std::vector<Size> cameraResolutions;
	std::copy_if(camera3Resolutions.begin(), camera3Resolutions.end(),
		     std::back_inserter(cameraResolutions),
		     [&](const Size &res) { return res < maxRes; });

	/*
	 * The Camera3 specification suggests adding 1/2 and 1/4 of the maximum
	 * resolution.
	 */
	for (unsigned int divider = 2;; divider <<= 1) {
		Size derivedSize{
			maxRes.width / divider,
			maxRes.height / divider,
		};

		if (derivedSize.width < 320 ||
		    derivedSize.height < 240)
			break;

		cameraResolutions.push_back(derivedSize);
	}
	cameraResolutions.push_back(maxRes);

	/* Remove duplicated entries from the list of supported resolutions. */
	std::sort(cameraResolutions.begin(), cameraResolutions.end());
	auto last = std::unique(cameraResolutions.begin(), cameraResolutions.end());
	cameraResolutions.erase(last, cameraResolutions.end());

	/*
	 * Build the list of supported camera formats.
	 *
	 * To each Android format a list of compatible libcamera formats is
	 * associated. The first libcamera format that tests successful is added
	 * to the format translation map used when configuring the streams.
	 * It is then tested against the list of supported camera resolutions to
	 * build the stream configuration map reported through the camera static
	 * metadata.
	 */
	Size maxJpegSize;
	for (const auto &format : camera3FormatsMap) {
		int androidFormat = format.first;
		const Camera3Format &camera3Format = format.second;
		const std::vector<PixelFormat> &libcameraFormats =
			camera3Format.libcameraFormats;

		LOG(HAL, Debug) << "Trying to map Android format "
				<< camera3Format.name;

		/*
		 * JPEG is always supported, either produced directly by the
		 * camera, or encoded in the HAL.
		 */
		if (androidFormat == HAL_PIXEL_FORMAT_BLOB) {
			formatsMap_[androidFormat] = formats::MJPEG;
			LOG(HAL, Debug) << "Mapped Android format "
					<< camera3Format.name << " to "
					<< formats::MJPEG.toString()
					<< " (fixed mapping)";
			continue;
		}

		/*
		 * Test the libcamera formats that can produce images
		 * compatible with the format defined by Android.
		 */
		PixelFormat mappedFormat;
		for (const PixelFormat &pixelFormat : libcameraFormats) {

			LOG(HAL, Debug) << "Testing " << pixelFormat.toString();

			/*
			 * The stream configuration size can be adjusted,
			 * not the pixel format.
			 *
			 * \todo This could be simplified once all pipeline
			 * handlers will report the StreamFormats list of
			 * supported formats.
			 */
			cfg.pixelFormat = pixelFormat;

			CameraConfiguration::Status status = cameraConfig->validate();
			if (status != CameraConfiguration::Invalid &&
			    cfg.pixelFormat == pixelFormat) {
				mappedFormat = pixelFormat;
				break;
			}
		}

		if (!mappedFormat.isValid()) {
			/* If the format is not mandatory, skip it. */
			if (!camera3Format.mandatory)
				continue;

			LOG(HAL, Error)
				<< "Failed to map mandatory Android format "
				<< camera3Format.name << " ("
				<< utils::hex(androidFormat) << "): aborting";
			return -EINVAL;
		}

		/*
		 * Record the mapping and then proceed to generate the
		 * stream configurations map, by testing the image resolutions.
		 */
		formatsMap_[androidFormat] = mappedFormat;
		LOG(HAL, Debug) << "Mapped Android format "
				<< camera3Format.name << " to "
				<< mappedFormat.toString();

		std::vector<Size> resolutions;
		const PixelFormatInfo &info = PixelFormatInfo::info(mappedFormat);
		switch (info.colourEncoding) {
		case PixelFormatInfo::ColourEncodingRAW:
			if (info.bitsPerPixel != 16)
				continue;

			rawStreamAvailable_ = true;
			resolutions = initializeRawResolutions(mappedFormat);
			break;

		case PixelFormatInfo::ColourEncodingYUV:
		case PixelFormatInfo::ColourEncodingRGB:
			/*
			 * We support enumerating RGB streams here to allow
			 * mapping IMPLEMENTATION_DEFINED format to RGB.
			 */
			resolutions = initializeYUVResolutions(mappedFormat,
							       cameraResolutions);
			break;
		}

		for (const Size &res : resolutions) {
			/*
			 * Configure the Camera with the collected format and
			 * resolution to get an updated list of controls.
			 *
			 * \todo Avoid the need to configure the camera when
			 * redesigning the configuration API.
			 */
			cfg.size = res;
			int ret = camera_->configure(cameraConfig.get());
			if (ret)
				return ret;

			const ControlInfoMap &controls = camera_->controls();
			const auto frameDurations = controls.find(
				&controls::FrameDurationLimits);
			if (frameDurations == controls.end()) {
				LOG(HAL, Error)
					<< "Camera does not report frame durations";
				return -EINVAL;
			}

			int64_t minFrameDuration = frameDurations->second.min().get<int64_t>() * 1000;
			int64_t maxFrameDuration = frameDurations->second.max().get<int64_t>() * 1000;

			/*
			 * Cap min frame duration to 30 FPS with 1% tolerance.
			 *
			 * 30 frames per second has been validated as the most
			 * opportune frame rate for quality tuning, and power
			 * vs performances budget on Intel IPU3-based
			 * Chromebooks.
			 *
			 * \todo This is a platform-specific decision that needs
			 * to be abstracted and delegated to the configuration
			 * file.
			 *
			 * \todo libcamera only allows to control frame duration
			 * through the per-request controls::FrameDuration
			 * control. If we cap the durations here, we should be
			 * capable of configuring the camera to operate at such
			 * duration without requiring to have the FrameDuration
			 * control to be specified for each Request. Defer this
			 * to the in-development configuration API rework.
			 */
			int64_t minFrameDurationCap = 1e9 / 30.0;
			if (minFrameDuration < minFrameDurationCap) {
				float tolerance =
					(minFrameDurationCap - minFrameDuration) * 100.0 / minFrameDurationCap;

				/*
				 * If the tolerance is less than 1%, do not cap
				 * the frame duration.
				 */
				if (tolerance > 1.0)
					minFrameDuration = minFrameDurationCap;
			}

			streamConfigurations_.push_back({
				res, androidFormat, minFrameDuration, maxFrameDuration,
			});

			/*
			 * If the format is HAL_PIXEL_FORMAT_YCbCr_420_888
			 * from which JPEG is produced, add an entry for
			 * the JPEG stream.
			 *
			 * \todo Wire the JPEG encoder to query the supported
			 * sizes provided a list of formats it can encode.
			 *
			 * \todo Support JPEG streams produced by the camera
			 * natively.
			 *
			 * \todo HAL_PIXEL_FORMAT_BLOB is a 'stalling' format,
			 * its duration should take into account the time
			 * required for the YUV to JPEG encoding. For now
			 * use the same frame durations as collected for
			 * the YUV/RGB streams.
			 */
			if (androidFormat == HAL_PIXEL_FORMAT_YCbCr_420_888) {
				streamConfigurations_.push_back({
					res, HAL_PIXEL_FORMAT_BLOB,
					minFrameDuration, maxFrameDuration,
				});
				maxJpegSize = std::max(maxJpegSize, res);
			}

			maxFrameDuration_ = std::max(maxFrameDuration_,
						     maxFrameDuration);
		}

		/*
		 * \todo Calculate the maximum JPEG buffer size by asking the
		 * encoder giving the maximum frame size required.
		 */
		maxJpegBufferSize_ = maxJpegSize.width * maxJpegSize.height * 1.5;
	}

	LOG(HAL, Debug) << "Collected stream configuration map: ";
	for (const auto &entry : streamConfigurations_)
		LOG(HAL, Debug) << "{ " << entry.resolution.toString() << " - "
				<< utils::hex(entry.androidFormat) << " }";

	return 0;
}

int CameraCapabilities::initializeStaticMetadata()
{
	staticMetadata_ = std::make_unique<CameraMetadata>(64, 1024);
	if (!staticMetadata_->isValid()) {
		LOG(HAL, Error) << "Failed to allocate static metadata";
		staticMetadata_.reset();
		return -EINVAL;
	}

	/*
	 * Generate and apply a new configuration for the Viewfinder role to
	 * collect control limits and properties from a known state.
	 */
	std::unique_ptr<CameraConfiguration> cameraConfig =
		camera_->generateConfiguration({ StreamRole::Viewfinder });
	if (!cameraConfig) {
		LOG(HAL, Error) << "Failed to generate camera configuration";
		staticMetadata_.reset();
		return -ENODEV;
	}

	int ret = camera_->configure(cameraConfig.get());
	if (ret) {
		LOG(HAL, Error) << "Failed to initialize the camera state";
		staticMetadata_.reset();
		return ret;
	}

	const ControlInfoMap &controlsInfo = camera_->controls();
	const ControlList &properties = camera_->properties();

	availableCharacteristicsKeys_ = {
		ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
		ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
		ANDROID_CONTROL_AE_AVAILABLE_MODES,
		ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
		ANDROID_CONTROL_AE_COMPENSATION_RANGE,
		ANDROID_CONTROL_AE_COMPENSATION_STEP,
		ANDROID_CONTROL_AE_LOCK_AVAILABLE,
		ANDROID_CONTROL_AF_AVAILABLE_MODES,
		ANDROID_CONTROL_AVAILABLE_EFFECTS,
		ANDROID_CONTROL_AVAILABLE_MODES,
		ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
		ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
		ANDROID_CONTROL_AWB_AVAILABLE_MODES,
		ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
		ANDROID_CONTROL_MAX_REGIONS,
		ANDROID_CONTROL_SCENE_MODE_OVERRIDES,
		ANDROID_FLASH_INFO_AVAILABLE,
		ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL,
		ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
		ANDROID_JPEG_MAX_SIZE,
		ANDROID_LENS_FACING,
		ANDROID_LENS_INFO_AVAILABLE_APERTURES,
		ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS,
		ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
		ANDROID_LENS_INFO_HYPERFOCAL_DISTANCE,
		ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE,
		ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
		ANDROID_REQUEST_AVAILABLE_CAPABILITIES,
		ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS,
		ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS,
		ANDROID_REQUEST_PARTIAL_RESULT_COUNT,
		ANDROID_REQUEST_PIPELINE_MAX_DEPTH,
		ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
		ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS,
		ANDROID_SCALER_AVAILABLE_STALL_DURATIONS,
		ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
		ANDROID_SCALER_CROPPING_TYPE,
		ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES,
		ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
		ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
		ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE,
		ANDROID_SENSOR_INFO_MAX_FRAME_DURATION,
		ANDROID_SENSOR_INFO_PHYSICAL_SIZE,
		ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
		ANDROID_SENSOR_INFO_SENSITIVITY_RANGE,
		ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE,
		ANDROID_SENSOR_ORIENTATION,
		ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
		ANDROID_STATISTICS_INFO_MAX_FACE_COUNT,
		ANDROID_SYNC_MAX_LATENCY,
	};

	availableRequestKeys_ = {
		ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
		ANDROID_CONTROL_AE_ANTIBANDING_MODE,
		ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
		ANDROID_CONTROL_AE_LOCK,
		ANDROID_CONTROL_AE_MODE,
		ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
		ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
		ANDROID_CONTROL_AF_MODE,
		ANDROID_CONTROL_AF_TRIGGER,
		ANDROID_CONTROL_AWB_LOCK,
		ANDROID_CONTROL_AWB_MODE,
		ANDROID_CONTROL_CAPTURE_INTENT,
		ANDROID_CONTROL_EFFECT_MODE,
		ANDROID_CONTROL_MODE,
		ANDROID_CONTROL_SCENE_MODE,
		ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
		ANDROID_FLASH_MODE,
		ANDROID_JPEG_ORIENTATION,
		ANDROID_JPEG_QUALITY,
		ANDROID_JPEG_THUMBNAIL_QUALITY,
		ANDROID_JPEG_THUMBNAIL_SIZE,
		ANDROID_LENS_APERTURE,
		ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
		ANDROID_NOISE_REDUCTION_MODE,
		ANDROID_SCALER_CROP_REGION,
		ANDROID_STATISTICS_FACE_DETECT_MODE
	};

	availableResultKeys_ = {
		ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
		ANDROID_CONTROL_AE_ANTIBANDING_MODE,
		ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
		ANDROID_CONTROL_AE_LOCK,
		ANDROID_CONTROL_AE_MODE,
		ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
		ANDROID_CONTROL_AE_STATE,
		ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
		ANDROID_CONTROL_AF_MODE,
		ANDROID_CONTROL_AF_STATE,
		ANDROID_CONTROL_AF_TRIGGER,
		ANDROID_CONTROL_AWB_LOCK,
		ANDROID_CONTROL_AWB_MODE,
		ANDROID_CONTROL_AWB_STATE,
		ANDROID_CONTROL_CAPTURE_INTENT,
		ANDROID_CONTROL_EFFECT_MODE,
		ANDROID_CONTROL_MODE,
		ANDROID_CONTROL_SCENE_MODE,
		ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
		ANDROID_FLASH_MODE,
		ANDROID_FLASH_STATE,
		ANDROID_JPEG_GPS_COORDINATES,
		ANDROID_JPEG_GPS_PROCESSING_METHOD,
		ANDROID_JPEG_GPS_TIMESTAMP,
		ANDROID_JPEG_ORIENTATION,
		ANDROID_JPEG_QUALITY,
		ANDROID_JPEG_SIZE,
		ANDROID_JPEG_THUMBNAIL_QUALITY,
		ANDROID_JPEG_THUMBNAIL_SIZE,
		ANDROID_LENS_APERTURE,
		ANDROID_LENS_FOCAL_LENGTH,
		ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
		ANDROID_LENS_STATE,
		ANDROID_NOISE_REDUCTION_MODE,
		ANDROID_REQUEST_PIPELINE_DEPTH,
		ANDROID_SCALER_CROP_REGION,
		ANDROID_SENSOR_EXPOSURE_TIME,
		ANDROID_SENSOR_FRAME_DURATION,
		ANDROID_SENSOR_ROLLING_SHUTTER_SKEW,
		ANDROID_SENSOR_TEST_PATTERN_MODE,
		ANDROID_SENSOR_TIMESTAMP,
		ANDROID_STATISTICS_FACE_DETECT_MODE,
		ANDROID_STATISTICS_LENS_SHADING_MAP_MODE,
		ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE,
		ANDROID_STATISTICS_SCENE_FLICKER,
	};

	/* Color correction static metadata. */
	{
		std::vector<uint8_t> data;
		data.reserve(3);
		const auto &infoMap = controlsInfo.find(&controls::draft::ColorCorrectionAberrationMode);
		if (infoMap != controlsInfo.end()) {
			for (const auto &value : infoMap->second.values())
				data.push_back(value.get<int32_t>());
		} else {
			data.push_back(ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF);
		}
		staticMetadata_->addEntry(ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
					  data);
	}

	/* Control static metadata. */
	std::vector<uint8_t> aeAvailableAntiBandingModes = {
		ANDROID_CONTROL_AE_ANTIBANDING_MODE_OFF,
		ANDROID_CONTROL_AE_ANTIBANDING_MODE_50HZ,
		ANDROID_CONTROL_AE_ANTIBANDING_MODE_60HZ,
		ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
				  aeAvailableAntiBandingModes);

	std::vector<uint8_t> aeAvailableModes = {
		ANDROID_CONTROL_AE_MODE_ON,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AE_AVAILABLE_MODES,
				  aeAvailableModes);

	std::vector<int32_t> aeCompensationRange = {
		0, 0,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AE_COMPENSATION_RANGE,
				  aeCompensationRange);

	const camera_metadata_rational_t aeCompensationStep[] = {
		{ 0, 1 }
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AE_COMPENSATION_STEP,
				  aeCompensationStep);

	std::vector<uint8_t> availableAfModes = {
		ANDROID_CONTROL_AF_MODE_OFF,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AF_AVAILABLE_MODES,
				  availableAfModes);

	std::vector<uint8_t> availableEffects = {
		ANDROID_CONTROL_EFFECT_MODE_OFF,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AVAILABLE_EFFECTS,
				  availableEffects);

	std::vector<uint8_t> availableSceneModes = {
		ANDROID_CONTROL_SCENE_MODE_DISABLED,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
				  availableSceneModes);

	std::vector<uint8_t> availableStabilizationModes = {
		ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
				  availableStabilizationModes);

	/*
	 * \todo Inspect the camera capabilities to report the available
	 * AWB modes. Default to AUTO as CTS tests require it.
	 */
	std::vector<uint8_t> availableAwbModes = {
		ANDROID_CONTROL_AWB_MODE_AUTO,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AWB_AVAILABLE_MODES,
				  availableAwbModes);

	std::vector<int32_t> availableMaxRegions = {
		0, 0, 0,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_MAX_REGIONS,
				  availableMaxRegions);

	std::vector<uint8_t> sceneModesOverride = {
		ANDROID_CONTROL_AE_MODE_ON,
		ANDROID_CONTROL_AWB_MODE_AUTO,
		ANDROID_CONTROL_AF_MODE_OFF,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_SCENE_MODE_OVERRIDES,
				  sceneModesOverride);

	uint8_t aeLockAvailable = ANDROID_CONTROL_AE_LOCK_AVAILABLE_FALSE;
	staticMetadata_->addEntry(ANDROID_CONTROL_AE_LOCK_AVAILABLE,
				  aeLockAvailable);

	uint8_t awbLockAvailable = ANDROID_CONTROL_AWB_LOCK_AVAILABLE_FALSE;
	staticMetadata_->addEntry(ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
				  awbLockAvailable);

	char availableControlModes = ANDROID_CONTROL_MODE_AUTO;
	staticMetadata_->addEntry(ANDROID_CONTROL_AVAILABLE_MODES,
				  availableControlModes);

	/* JPEG static metadata. */

	/*
	 * Create the list of supported thumbnail sizes by inspecting the
	 * available JPEG resolutions collected in streamConfigurations_ and
	 * generate one entry for each aspect ratio.
	 *
	 * The JPEG thumbnailer can freely scale, so pick an arbitrary
	 * (160, 160) size as the bounding rectangle, which is then cropped to
	 * the different supported aspect ratios.
	 */
	constexpr Size maxJpegThumbnail(160, 160);
	std::vector<Size> thumbnailSizes;
	thumbnailSizes.push_back({ 0, 0 });
	for (const auto &entry : streamConfigurations_) {
		if (entry.androidFormat != HAL_PIXEL_FORMAT_BLOB)
			continue;

		Size thumbnailSize = maxJpegThumbnail
				     .boundedToAspectRatio({ entry.resolution.width,
							     entry.resolution.height });
		thumbnailSizes.push_back(thumbnailSize);
	}

	std::sort(thumbnailSizes.begin(), thumbnailSizes.end());
	auto last = std::unique(thumbnailSizes.begin(), thumbnailSizes.end());
	thumbnailSizes.erase(last, thumbnailSizes.end());

	/* Transform sizes in to a list of integers that can be consumed. */
	std::vector<int32_t> thumbnailEntries;
	thumbnailEntries.reserve(thumbnailSizes.size() * 2);
	for (const auto &size : thumbnailSizes) {
		thumbnailEntries.push_back(size.width);
		thumbnailEntries.push_back(size.height);
	}
	staticMetadata_->addEntry(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
				  thumbnailEntries);

	staticMetadata_->addEntry(ANDROID_JPEG_MAX_SIZE, maxJpegBufferSize_);

	/* Sensor static metadata. */
	std::array<int32_t, 2> pixelArraySize;
	{
		const Size &size = properties.get(properties::PixelArraySize);
		pixelArraySize[0] = size.width;
		pixelArraySize[1] = size.height;
		staticMetadata_->addEntry(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
					  pixelArraySize);
	}

	if (properties.contains(properties::UnitCellSize)) {
		const Size &cellSize = properties.get<Size>(properties::UnitCellSize);
		std::array<float, 2> physicalSize{
			cellSize.width * pixelArraySize[0] / 1e6f,
			cellSize.height * pixelArraySize[1] / 1e6f
		};
		staticMetadata_->addEntry(ANDROID_SENSOR_INFO_PHYSICAL_SIZE,
					  physicalSize);
	}

	{
		const Span<const Rectangle> &rects =
			properties.get(properties::PixelArrayActiveAreas);
		std::vector<int32_t> data{
			static_cast<int32_t>(rects[0].x),
			static_cast<int32_t>(rects[0].y),
			static_cast<int32_t>(rects[0].width),
			static_cast<int32_t>(rects[0].height),
		};
		staticMetadata_->addEntry(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
					  data);
	}

	int32_t sensitivityRange[] = {
		32, 2400,
	};
	staticMetadata_->addEntry(ANDROID_SENSOR_INFO_SENSITIVITY_RANGE,
				  sensitivityRange);

	/* Report the color filter arrangement if the camera reports it. */
	if (properties.contains(properties::draft::ColorFilterArrangement)) {
		uint8_t filterArr = properties.get(properties::draft::ColorFilterArrangement);
		staticMetadata_->addEntry(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
					  filterArr);
	}

	const auto &exposureInfo = controlsInfo.find(&controls::ExposureTime);
	if (exposureInfo != controlsInfo.end()) {
		int64_t exposureTimeRange[2] = {
			exposureInfo->second.min().get<int32_t>() * 1000LL,
			exposureInfo->second.max().get<int32_t>() * 1000LL,
		};
		staticMetadata_->addEntry(ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE,
					  exposureTimeRange, 2);
	}

	staticMetadata_->addEntry(ANDROID_SENSOR_ORIENTATION, orientation_);

	std::vector<int32_t> testPatternModes = {
		ANDROID_SENSOR_TEST_PATTERN_MODE_OFF
	};
	const auto &testPatternsInfo =
		controlsInfo.find(&controls::draft::TestPatternMode);
	if (testPatternsInfo != controlsInfo.end()) {
		const auto &values = testPatternsInfo->second.values();
		ASSERT(!values.empty());
		for (const auto &value : values) {
			switch (value.get<int32_t>()) {
			case controls::draft::TestPatternModeOff:
				/*
				 * ANDROID_SENSOR_TEST_PATTERN_MODE_OFF is
				 * already in testPatternModes.
				 */
				break;

			case controls::draft::TestPatternModeSolidColor:
				testPatternModes.push_back(
					ANDROID_SENSOR_TEST_PATTERN_MODE_SOLID_COLOR);
				break;

			case controls::draft::TestPatternModeColorBars:
				testPatternModes.push_back(
					ANDROID_SENSOR_TEST_PATTERN_MODE_COLOR_BARS);
				break;

			case controls::draft::TestPatternModeColorBarsFadeToGray:
				testPatternModes.push_back(
					ANDROID_SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY);
				break;

			case controls::draft::TestPatternModePn9:
				testPatternModes.push_back(
					ANDROID_SENSOR_TEST_PATTERN_MODE_PN9);
				break;

			case controls::draft::TestPatternModeCustom1:
				/* We don't support this yet. */
				break;

			default:
				LOG(HAL, Error) << "Unknown test pattern mode: "
						<< value.get<int32_t>();
				continue;
			}
		}
	}
	staticMetadata_->addEntry(ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES,
				  testPatternModes);

	uint8_t timestampSource = ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN;
	staticMetadata_->addEntry(ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE,
				  timestampSource);

	staticMetadata_->addEntry(ANDROID_SENSOR_INFO_MAX_FRAME_DURATION,
				  maxFrameDuration_);

	/* Statistics static metadata. */
	uint8_t faceDetectMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF;
	staticMetadata_->addEntry(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
				  faceDetectMode);

	int32_t maxFaceCount = 0;
	staticMetadata_->addEntry(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT,
				  maxFaceCount);

	{
		std::vector<uint8_t> data;
		data.reserve(2);
		const auto &infoMap = controlsInfo.find(&controls::draft::LensShadingMapMode);
		if (infoMap != controlsInfo.end()) {
			for (const auto &value : infoMap->second.values())
				data.push_back(value.get<int32_t>());
		} else {
			data.push_back(ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF);
		}
		staticMetadata_->addEntry(ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
					  data);
	}

	/* Sync static metadata. */
	setMetadata(staticMetadata_.get(), ANDROID_SYNC_MAX_LATENCY,
		    controlsInfo, controls::draft::MaxLatency,
		    ControlRange::Def,
		    ANDROID_SYNC_MAX_LATENCY_UNKNOWN);

	/* Flash static metadata. */
	char flashAvailable = ANDROID_FLASH_INFO_AVAILABLE_FALSE;
	staticMetadata_->addEntry(ANDROID_FLASH_INFO_AVAILABLE,
				  flashAvailable);

	/* Lens static metadata. */
	std::vector<float> lensApertures = {
		2.53 / 100,
	};
	staticMetadata_->addEntry(ANDROID_LENS_INFO_AVAILABLE_APERTURES,
				  lensApertures);

	uint8_t lensFacing;
	switch (facing_) {
	default:
	case CAMERA_FACING_FRONT:
		lensFacing = ANDROID_LENS_FACING_FRONT;
		break;
	case CAMERA_FACING_BACK:
		lensFacing = ANDROID_LENS_FACING_BACK;
		break;
	case CAMERA_FACING_EXTERNAL:
		lensFacing = ANDROID_LENS_FACING_EXTERNAL;
		break;
	}
	staticMetadata_->addEntry(ANDROID_LENS_FACING, lensFacing);

	std::vector<float> lensFocalLengths = {
		1,
	};
	staticMetadata_->addEntry(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS,
				  lensFocalLengths);

	std::vector<uint8_t> opticalStabilizations = {
		ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF,
	};
	staticMetadata_->addEntry(ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
				  opticalStabilizations);

	float hypeFocalDistance = 0;
	staticMetadata_->addEntry(ANDROID_LENS_INFO_HYPERFOCAL_DISTANCE,
				  hypeFocalDistance);

	float minFocusDistance = 0;
	staticMetadata_->addEntry(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE,
				  minFocusDistance);

	/* Noise reduction modes. */
	{
		std::vector<uint8_t> data;
		data.reserve(5);
		const auto &infoMap = controlsInfo.find(&controls::draft::NoiseReductionMode);
		if (infoMap != controlsInfo.end()) {
			for (const auto &value : infoMap->second.values())
				data.push_back(value.get<int32_t>());
		} else {
			data.push_back(ANDROID_NOISE_REDUCTION_MODE_OFF);
		}
		staticMetadata_->addEntry(ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
					  data);
	}

	/* Scaler static metadata. */

	/*
	 * \todo The digital zoom factor is a property that depends on the
	 * desired output configuration and the sensor frame size input to the
	 * ISP. This information is not available to the Android HAL, not at
	 * initialization time at least.
	 *
	 * As a workaround rely on pipeline handlers initializing the
	 * ScalerCrop control with the camera default configuration and use the
	 * maximum and minimum crop rectangles to calculate the digital zoom
	 * factor.
	 */
	float maxZoom = 1.0f;
	const auto scalerCrop = controlsInfo.find(&controls::ScalerCrop);
	if (scalerCrop != controlsInfo.end()) {
		Rectangle min = scalerCrop->second.min().get<Rectangle>();
		Rectangle max = scalerCrop->second.max().get<Rectangle>();
		maxZoom = std::min(1.0f * max.width / min.width,
				   1.0f * max.height / min.height);
	}
	staticMetadata_->addEntry(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
				  maxZoom);

	std::vector<uint32_t> availableStreamConfigurations;
	std::vector<int64_t> minFrameDurations;
	int maxYUVFps = 0;
	Size maxYUVSize;

	availableStreamConfigurations.reserve(streamConfigurations_.size() * 4);
	minFrameDurations.reserve(streamConfigurations_.size() * 4);

	for (const auto &entry : streamConfigurations_) {
		/*
		 * Filter out YUV streams not capable of running at 30 FPS.
		 *
		 * This requirement comes from CTS RecordingTest failures most
		 * probably related to a requirement of the camcoder video
		 * recording profile. Inspecting the Intel IPU3 HAL
		 * implementation confirms this but no reference has been found
		 * in the metadata documentation.
		 *
		 * Calculate FPS as CTS does: see
		 * Camera2SurfaceViewTestCase.java:getSuitableFpsRangeForDuration()
		 */
		unsigned int fps = static_cast<unsigned int>
				   (floor(1e9 / entry.minFrameDurationNsec + 0.05f));
		if (entry.androidFormat != HAL_PIXEL_FORMAT_BLOB && fps < 30)
			continue;

		/*
		 * Collect the FPS of the maximum YUV output size to populate
		 * AE_AVAILABLE_TARGET_FPS_RANGE
		 */
		if (entry.androidFormat == HAL_PIXEL_FORMAT_YCbCr_420_888 &&
		    entry.resolution > maxYUVSize) {
			maxYUVSize = entry.resolution;
			maxYUVFps = fps;
		}

		/* Stream configuration map. */
		availableStreamConfigurations.push_back(entry.androidFormat);
		availableStreamConfigurations.push_back(entry.resolution.width);
		availableStreamConfigurations.push_back(entry.resolution.height);
		availableStreamConfigurations.push_back(
			ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT);

		/* Per-stream durations. */
		minFrameDurations.push_back(entry.androidFormat);
		minFrameDurations.push_back(entry.resolution.width);
		minFrameDurations.push_back(entry.resolution.height);
		minFrameDurations.push_back(entry.minFrameDurationNsec);

		LOG(HAL, Debug)
			<< "Output Stream: " << utils::hex(entry.androidFormat)
			<< " (" << entry.resolution.toString() << ")["
			<< entry.minFrameDurationNsec << "]"
			<< "@" << fps;
	}
	staticMetadata_->addEntry(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
				  availableStreamConfigurations);

	staticMetadata_->addEntry(ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS,
				  minFrameDurations);

	/*
	 * Register to the camera service {min, max} and {max, max} with
	 * 'max' being the larger YUV stream maximum frame rate and 'min' being
	 * the globally minimum frame rate rounded to the next largest integer
	 * as the camera service expects the camera maximum frame duration to be
	 * smaller than 10^9 / minFps.
	 */
	int32_t minFps = std::ceil(1e9 / maxFrameDuration_);
	int32_t availableAeFpsTarget[] = {
		minFps, maxYUVFps, maxYUVFps, maxYUVFps,
	};
	staticMetadata_->addEntry(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
				  availableAeFpsTarget);

	std::vector<int64_t> availableStallDurations;
	for (const auto &entry : streamConfigurations_) {
		if (entry.androidFormat != HAL_PIXEL_FORMAT_BLOB)
			continue;

		availableStallDurations.push_back(entry.androidFormat);
		availableStallDurations.push_back(entry.resolution.width);
		availableStallDurations.push_back(entry.resolution.height);
		availableStallDurations.push_back(entry.minFrameDurationNsec);
	}
	staticMetadata_->addEntry(ANDROID_SCALER_AVAILABLE_STALL_DURATIONS,
				  availableStallDurations);

	uint8_t croppingType = ANDROID_SCALER_CROPPING_TYPE_CENTER_ONLY;
	staticMetadata_->addEntry(ANDROID_SCALER_CROPPING_TYPE, croppingType);

	/* Request static metadata. */
	int32_t partialResultCount = 1;
	staticMetadata_->addEntry(ANDROID_REQUEST_PARTIAL_RESULT_COUNT,
				  partialResultCount);

	{
		/* Default the value to 2 if not reported by the camera. */
		uint8_t maxPipelineDepth = 2;
		const auto &infoMap = controlsInfo.find(&controls::draft::PipelineDepth);
		if (infoMap != controlsInfo.end())
			maxPipelineDepth = infoMap->second.max().get<int32_t>();
		staticMetadata_->addEntry(ANDROID_REQUEST_PIPELINE_MAX_DEPTH,
					  maxPipelineDepth);
	}

	/* LIMITED does not support reprocessing. */
	uint32_t maxNumInputStreams = 0;
	staticMetadata_->addEntry(ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS,
				  maxNumInputStreams);

	/* Number of { RAW, YUV, JPEG } supported output streams */
	int32_t numOutStreams[] = { rawStreamAvailable_, 2, 1 };
	staticMetadata_->addEntry(ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS,
				  numOutStreams);

	/* Check capabilities */
	capabilities_ = computeCapabilities();
	std::vector<camera_metadata_enum_android_request_available_capabilities>
		capsVec(capabilities_.begin(), capabilities_.end());
	staticMetadata_->addEntry(ANDROID_REQUEST_AVAILABLE_CAPABILITIES, capsVec);

	computeHwLevel(capabilities_);
	staticMetadata_->addEntry(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL, hwLevel_);

	LOG(HAL, Info)
		<< "Hardware level: " << hwLevelStrings.find(hwLevel_)->second;

	staticMetadata_->addEntry(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
				  std::vector<int32_t>(availableCharacteristicsKeys_.begin(),
						       availableCharacteristicsKeys_.end()));

	staticMetadata_->addEntry(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS,
				  std::vector<int32_t>(availableRequestKeys_.begin(),
						       availableRequestKeys_.end()));

	staticMetadata_->addEntry(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS,
				  std::vector<int32_t>(availableResultKeys_.begin(),
						       availableResultKeys_.end()));

	if (!staticMetadata_->isValid()) {
		LOG(HAL, Error) << "Failed to construct static metadata";
		staticMetadata_.reset();
		return -EINVAL;
	}

	if (staticMetadata_->resized()) {
		auto [entryCount, dataCount] = staticMetadata_->usage();
		LOG(HAL, Info)
			<< "Static metadata resized: " << entryCount
			<< " entries and " << dataCount << " bytes used";
	}

	return 0;
}

/* Translate Android format code to libcamera pixel format. */
PixelFormat CameraCapabilities::toPixelFormat(int format) const
{
	auto it = formatsMap_.find(format);
	if (it == formatsMap_.end()) {
		LOG(HAL, Error) << "Requested format " << utils::hex(format)
				<< " not supported";
		return PixelFormat();
	}

	return it->second;
}

std::unique_ptr<CameraMetadata> CameraCapabilities::requestTemplateManual() const
{
	if (!capabilities_.count(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
		LOG(HAL, Error) << "Manual template not supported";
		return nullptr;
	}

	std::unique_ptr<CameraMetadata> manualTemplate = requestTemplatePreview();
	if (!manualTemplate)
		return nullptr;

	return manualTemplate;
}

std::unique_ptr<CameraMetadata> CameraCapabilities::requestTemplatePreview() const
{
	/*
	 * Give initial hint of entries and number of bytes to be allocated.
	 * It is deliberate that the hint is slightly larger than required, to
	 * avoid resizing the container.
	 *
	 * CameraMetadata is capable of resizing the container on the fly, if
	 * adding a new entry will exceed its capacity.
	 */
	auto requestTemplate = std::make_unique<CameraMetadata>(22, 38);
	if (!requestTemplate->isValid()) {
		return nullptr;
	}

	/* Get the FPS range registered in the static metadata. */
	camera_metadata_ro_entry_t entry;
	bool found = staticMetadata_->getEntry(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
					       &entry);
	if (!found) {
		LOG(HAL, Error) << "Cannot create capture template without FPS range";
		return nullptr;
	}

	/*
	 * Assume the AE_AVAILABLE_TARGET_FPS_RANGE static metadata
	 * has been assembled as {{min, max} {max, max}}.
	 */
	requestTemplate->addEntry(ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
				  entry.data.i32, 2);

	/*
	 * Get thumbnail sizes from static metadata and add the first non-zero
	 * size to the template.
	 */
	found = staticMetadata_->getEntry(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
					  &entry);
	ASSERT(found && entry.count >= 4);
	requestTemplate->addEntry(ANDROID_JPEG_THUMBNAIL_SIZE,
				  entry.data.i32 + 2, 2);

	uint8_t aeMode = ANDROID_CONTROL_AE_MODE_ON;
	requestTemplate->addEntry(ANDROID_CONTROL_AE_MODE, aeMode);

	int32_t aeExposureCompensation = 0;
	requestTemplate->addEntry(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
				  aeExposureCompensation);

	uint8_t aePrecaptureTrigger = ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_IDLE;
	requestTemplate->addEntry(ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
				  aePrecaptureTrigger);

	uint8_t aeLock = ANDROID_CONTROL_AE_LOCK_OFF;
	requestTemplate->addEntry(ANDROID_CONTROL_AE_LOCK, aeLock);

	uint8_t aeAntibandingMode = ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO;
	requestTemplate->addEntry(ANDROID_CONTROL_AE_ANTIBANDING_MODE,
				  aeAntibandingMode);

	uint8_t afMode = ANDROID_CONTROL_AF_MODE_OFF;
	requestTemplate->addEntry(ANDROID_CONTROL_AF_MODE, afMode);

	uint8_t afTrigger = ANDROID_CONTROL_AF_TRIGGER_IDLE;
	requestTemplate->addEntry(ANDROID_CONTROL_AF_TRIGGER, afTrigger);

	uint8_t awbMode = ANDROID_CONTROL_AWB_MODE_AUTO;
	requestTemplate->addEntry(ANDROID_CONTROL_AWB_MODE, awbMode);

	uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
	requestTemplate->addEntry(ANDROID_CONTROL_AWB_LOCK, awbLock);

	uint8_t flashMode = ANDROID_FLASH_MODE_OFF;
	requestTemplate->addEntry(ANDROID_FLASH_MODE, flashMode);

	uint8_t faceDetectMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF;
	requestTemplate->addEntry(ANDROID_STATISTICS_FACE_DETECT_MODE,
				  faceDetectMode);

	uint8_t noiseReduction = ANDROID_NOISE_REDUCTION_MODE_OFF;
	requestTemplate->addEntry(ANDROID_NOISE_REDUCTION_MODE,
				  noiseReduction);

	uint8_t aberrationMode = ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF;
	requestTemplate->addEntry(ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
				  aberrationMode);

	uint8_t controlMode = ANDROID_CONTROL_MODE_AUTO;
	requestTemplate->addEntry(ANDROID_CONTROL_MODE, controlMode);

	float lensAperture = 2.53 / 100;
	requestTemplate->addEntry(ANDROID_LENS_APERTURE, lensAperture);

	uint8_t opticalStabilization = ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
	requestTemplate->addEntry(ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
				  opticalStabilization);

	uint8_t captureIntent = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
	requestTemplate->addEntry(ANDROID_CONTROL_CAPTURE_INTENT,
				  captureIntent);

	return requestTemplate;
}

std::unique_ptr<CameraMetadata> CameraCapabilities::requestTemplateStill() const
{
	std::unique_ptr<CameraMetadata> stillTemplate = requestTemplatePreview();
	if (!stillTemplate)
		return nullptr;

	return stillTemplate;
}

std::unique_ptr<CameraMetadata> CameraCapabilities::requestTemplateVideo() const
{
	std::unique_ptr<CameraMetadata> previewTemplate = requestTemplatePreview();
	if (!previewTemplate)
		return nullptr;

	/*
	 * The video template requires a fixed FPS range. Everything else
	 * stays the same as the preview template.
	 */
	camera_metadata_ro_entry_t entry;
	staticMetadata_->getEntry(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
				  &entry);

	/*
	 * Assume the AE_AVAILABLE_TARGET_FPS_RANGE static metadata
	 * has been assembled as {{min, max} {max, max}}.
	 */
	previewTemplate->updateEntry(ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
				     entry.data.i32 + 2, 2);

	return previewTemplate;
}