text: // Copyright 2017 The Chromium Authors. All rights reserved.
  // 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_device_delegate.h"
  
  #include <algorithm>
  #include <cmath>
  #include <memory>
  #include <string>
  #include <utility>
  #include <vector>
  
  #include "ash/constants/ash_features.h"
  #include "base/bind.h"
  #include "base/callback_helpers.h"
  #include "base/no_destructor.h"
  #include "base/numerics/ranges.h"
  #include "base/posix/safe_strerror.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/trace_event/trace_event.h"
  #include "media/base/bind_to_current_loop.h"
  #include "media/capture/mojom/image_capture_types.h"
  #include "media/capture/video/blob_utils.h"
  #include "media/capture/video/chromeos/camera_3a_controller.h"
  #include "media/capture/video/chromeos/camera_app_device_bridge_impl.h"
  #include "media/capture/video/chromeos/camera_buffer_factory.h"
  #include "media/capture/video/chromeos/camera_hal_delegate.h"
  #include "media/capture/video/chromeos/camera_metadata_utils.h"
  #include "media/capture/video/chromeos/request_manager.h"
  #include "mojo/public/cpp/bindings/pending_remote.h"
  
  namespace media {
  
  namespace {
  
  constexpr char kBrightness[] = "com.google.control.brightness";
  constexpr char kBrightnessRange[] = "com.google.control.brightnessRange";
  constexpr char kContrast[] = "com.google.control.contrast";
  constexpr char kContrastRange[] = "com.google.control.contrastRange";
  constexpr char kPan[] = "com.google.control.pan";
  constexpr char kPanRange[] = "com.google.control.panRange";
  constexpr char kSaturation[] = "com.google.control.saturation";
  constexpr char kSaturationRange[] = "com.google.control.saturationRange";
  constexpr char kSharpness[] = "com.google.control.sharpness";
  constexpr char kSharpnessRange[] = "com.google.control.sharpnessRange";
  constexpr char kTilt[] = "com.google.control.tilt";
  constexpr char kTiltRange[] = "com.google.control.tiltRange";
  constexpr char kZoom[] = "com.google.control.zoom";
  constexpr char kZoomRange[] = "com.google.control.zoomRange";
  constexpr int32_t kColorTemperatureStep = 100;
  constexpr int32_t kMicroToNano = 1000;
  
  using AwbModeTemperatureMap = std::map<uint8_t, int32_t>;
  
  const AwbModeTemperatureMap& GetAwbModeTemperatureMap() {
    // https://source.android.com/devices/camera/camera3_3Amodes#auto-wb
    static const base::NoDestructor<AwbModeTemperatureMap> kAwbModeTemperatureMap(
        {
            {static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
                                      ANDROID_CONTROL_AWB_MODE_INCANDESCENT),
             2700},
            {static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
                                      ANDROID_CONTROL_AWB_MODE_FLUORESCENT),
             5000},
            {static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
                                      ANDROID_CONTROL_AWB_MODE_WARM_FLUORESCENT),
             3000},
            {static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
                                      ANDROID_CONTROL_AWB_MODE_DAYLIGHT),
             5500},
            {static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
                                      ANDROID_CONTROL_AWB_MODE_CLOUDY_DAYLIGHT),
             6500},
            {static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
                                      ANDROID_CONTROL_AWB_MODE_TWILIGHT),
             15000},
            {static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
                                      ANDROID_CONTROL_AWB_MODE_SHADE),
             7500},
        });
    return *kAwbModeTemperatureMap;
  }
  
  std::pair<int32_t, int32_t> GetTargetFrameRateRange(
      const cros::mojom::CameraMetadataPtr& static_metadata,
      int target_frame_rate,
      bool prefer_constant_frame_rate) {
    int32_t result_min = 0;
    int32_t result_max = 0;
    auto available_frame_rates = GetMetadataEntryAsSpan<int32_t>(
        static_metadata, cros::mojom::CameraMetadataTag::
                             ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
    for (size_t idx = 0; idx < available_frame_rates.size(); idx += 2) {
      int32_t min = available_frame_rates[idx];
      int32_t max = available_frame_rates[idx + 1];
      if (max != target_frame_rate) {
        continue;
      }
  
      if (result_min != 0) {
        if (prefer_constant_frame_rate && min <= result_min) {
          // If we prefer constant frame rate, we prefer a smaller range.
          continue;
        } else if (!prefer_constant_frame_rate && min >= result_min) {
          // Othwewise, we prefer a larger range.
          continue;
        }
      }
      result_min = min;
      result_max = max;
  
      if (prefer_constant_frame_rate && min == max) {
        break;
      }
    }
    return std::make_pair(result_min, result_max);
  }
  
  // The result of max_width and max_height could be zero if the stream
  // is not in the pre-defined configuration.
  void GetStreamResolutions(const cros::mojom::CameraMetadataPtr& static_metadata,
                            cros::mojom::Camera3StreamType stream_type,
                            cros::mojom::HalPixelFormat stream_format,
                            std::vector<gfx::Size>* resolutions) {
    const cros::mojom::CameraMetadataEntryPtr* stream_configurations =
        GetMetadataEntry(static_metadata,
                         cros::mojom::CameraMetadataTag::
                             ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
    DCHECK(stream_configurations);
    // The available stream configurations are stored as tuples of four int32s:
    // (hal_pixel_format, width, height, type) x n
    const size_t kStreamFormatOffset = 0;
    const size_t kStreamWidthOffset = 1;
    const size_t kStreamHeightOffset = 2;
    const size_t kStreamTypeOffset = 3;
    const size_t kStreamConfigurationSize = 4;
    int32_t* iter =
        reinterpret_cast<int32_t*>((*stream_configurations)->data.data());
    for (size_t i = 0; i < (*stream_configurations)->count;
         i += kStreamConfigurationSize) {
      auto format =
          static_cast<cros::mojom::HalPixelFormat>(iter[kStreamFormatOffset]);
      int32_t width = iter[kStreamWidthOffset];
      int32_t height = iter[kStreamHeightOffset];
      auto type =
          static_cast<cros::mojom::Camera3StreamType>(iter[kStreamTypeOffset]);
      iter += kStreamConfigurationSize;
  
      if (type != stream_type || format != stream_format) {
        continue;
      }
  
      resolutions->emplace_back(width, height);
    }
  
    std::sort(resolutions->begin(), resolutions->end(),
              [](const gfx::Size& a, const gfx::Size& b) -> bool {
                return a.width() * a.height() < b.width() * b.height();
              });
  }
  
  // VideoCaptureDevice::TakePhotoCallback is given by the application and is used
  // to return the captured JPEG blob buffer.  The second base::OnceClosure is
  // created locally by the caller of TakePhoto(), and can be used to, for
  // example, restore some settings to the values before TakePhoto() is called to
  // facilitate the switch between photo and non-photo modes.
  void TakePhotoCallbackBundle(VideoCaptureDevice::TakePhotoCallback callback,
                               base::OnceClosure on_photo_taken_callback,
                               mojom::BlobPtr blob) {
    std::move(callback).Run(std::move(blob));
    std::move(on_photo_taken_callback).Run();
  }
  
  void SetFpsRangeInMetadata(cros::mojom::CameraMetadataPtr* settings,
                             int32_t min_frame_rate,
                             int32_t max_frame_rate) {
    const int32_t entry_length = 2;
  
    // CameraMetadata is represented as an uint8 array. According to the
    // definition of the FPS metadata tag, its data type is int32, so we
    // reinterpret_cast here.
    std::vector<uint8_t> fps_range(sizeof(int32_t) * entry_length);
    auto* fps_ptr = reinterpret_cast<int32_t*>(fps_range.data());
    fps_ptr[0] = min_frame_rate;
    fps_ptr[1] = max_frame_rate;
    cros::mojom::CameraMetadataEntryPtr e =
        cros::mojom::CameraMetadataEntry::New();
    e->tag = cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_TARGET_FPS_RANGE;
    e->type = cros::mojom::EntryType::TYPE_INT32;
    e->count = entry_length;
    e->data = std::move(fps_range);
  
    AddOrUpdateMetadataEntry(settings, std::move(e));
  }
  
  }  // namespace
  
  bool IsInputStream(StreamType stream_type) {
    // Currently there is only one input stream. Modify this method if there is
    // any other input streams.
    return stream_type == StreamType::kYUVInput;
  }
  
  StreamType StreamIdToStreamType(uint64_t stream_id) {
    switch (stream_id) {
      case 0:
        return StreamType::kPreviewOutput;
      case 1:
        return StreamType::kJpegOutput;
      case 2:
        return StreamType::kYUVInput;
      case 3:
        return StreamType::kYUVOutput;
      case 4:
        return StreamType::kRecordingOutput;
      default:
        return StreamType::kUnknown;
    }
  }  // namespace media
  
  std::string StreamTypeToString(StreamType stream_type) {
    switch (stream_type) {
      case StreamType::kPreviewOutput:
        return std::string("StreamType::kPreviewOutput");
      case StreamType::kJpegOutput:
        return std::string("StreamType::kJpegOutput");
      case StreamType::kYUVInput:
        return std::string("StreamType::kYUVInput");
      case StreamType::kYUVOutput:
        return std::string("StreamType::kYUVOutput");
      case StreamType::kRecordingOutput:
        return std::string("StreamType::kRecordingOutput");
      default:
        return std::string("Unknown StreamType value: ") +
               base::NumberToString(static_cast<int32_t>(stream_type));
    }
  }  // namespace media
  
  std::ostream& operator<<(std::ostream& os, StreamType stream_type) {
    return os << StreamTypeToString(stream_type);
  }
  
  StreamCaptureInterface::Plane::Plane() = default;
  
  StreamCaptureInterface::Plane::~Plane() = default;
  
  class CameraDeviceDelegate::StreamCaptureInterfaceImpl final
      : public StreamCaptureInterface {
   public:
    StreamCaptureInterfaceImpl(
        base::WeakPtr<CameraDeviceDelegate> camera_device_delegate)
        : camera_device_delegate_(std::move(camera_device_delegate)) {}
  
    void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr request,
                               base::OnceCallback<void(int32_t)> callback) final {
      if (camera_device_delegate_) {
        camera_device_delegate_->ProcessCaptureRequest(std::move(request),
                                                       std::move(callback));
      }
    }
  
    void Flush(base::OnceCallback<void(int32_t)> callback) final {
      if (camera_device_delegate_) {
        camera_device_delegate_->Flush(std::move(callback));
      }
    }
  
   private:
    const base::WeakPtr<CameraDeviceDelegate> camera_device_delegate_;
  };
  
  ResultMetadata::ResultMetadata() = default;
  ResultMetadata::~ResultMetadata() = default;
  
  CameraDeviceDelegate::CameraDeviceDelegate(
      VideoCaptureDeviceDescriptor device_descriptor,
      scoped_refptr<CameraHalDelegate> camera_hal_delegate,
      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner)
      : device_descriptor_(device_descriptor),
        camera_hal_delegate_(std::move(camera_hal_delegate)),
        ipc_task_runner_(std::move(ipc_task_runner)),
        camera_app_device_(
            CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
                device_descriptor_.device_id)) {}
  
  CameraDeviceDelegate::~CameraDeviceDelegate() = default;
  
  void CameraDeviceDelegate::AllocateAndStart(
      const base::flat_map<ClientType, VideoCaptureParams>& params,
      CameraDeviceContext* device_context) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    result_metadata_frame_number_for_photo_state_ = 0;
    result_metadata_frame_number_ = 0;
    is_set_awb_mode_ = false;
    is_set_brightness_ = false;
    is_set_contrast_ = false;
    is_set_exposure_compensation_ = false;
    is_set_exposure_time_ = false;
    is_set_focus_distance_ = false;
    is_set_iso_ = false;
    is_set_pan_ = false;
    is_set_saturation_ = false;
    is_set_sharpness_ = false;
    is_set_tilt_ = false;
    is_set_zoom_ = false;
  
    chrome_capture_params_ = params;
    device_context_ = device_context;
    device_context_->SetState(CameraDeviceContext::State::kStarting);
  
    auto camera_info = camera_hal_delegate_->GetCameraInfoFromDeviceId(
        device_descriptor_.device_id);
    if (camera_info.is_null()) {
      device_context_->SetErrorState(
          media::VideoCaptureError::kCrosHalV3DeviceDelegateFailedToGetCameraInfo,
          FROM_HERE, "Failed to get camera info");
      return;
    }
  
    SortCameraMetadata(&camera_info->static_camera_characteristics);
    static_metadata_ = std::move(camera_info->static_camera_characteristics);
  
    auto sensor_orientation = GetMetadataEntryAsSpan<int32_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_ORIENTATION);
    if (sensor_orientation.empty()) {
      device_context_->SetErrorState(
          media::VideoCaptureError::
              kCrosHalV3DeviceDelegateMissingSensorOrientationInfo,
          FROM_HERE, "Camera is missing required sensor orientation info");
      return;
    }
    auto rect = GetMetadataEntryAsSpan<int32_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
    active_array_size_ = gfx::Rect(rect[0], rect[1], rect[2], rect[3]);
    device_context_->SetSensorOrientation(sensor_orientation[0]);
  
    // |device_ops_| is bound after the BindNewPipeAndPassReceiver call.
    camera_hal_delegate_->OpenDevice(
        camera_hal_delegate_->GetCameraIdFromDeviceId(
            device_descriptor_.device_id),
        device_ops_.BindNewPipeAndPassReceiver(),
        BindToCurrentLoop(
            base::BindOnce(&CameraDeviceDelegate::OnOpenedDevice, GetWeakPtr())));
    device_ops_.set_disconnect_handler(base::BindOnce(
        &CameraDeviceDelegate::OnMojoConnectionError, GetWeakPtr()));
  }
  
  void CameraDeviceDelegate::StopAndDeAllocate(
      base::OnceClosure device_close_callback) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (!device_context_ ||
        device_context_->GetState() == CameraDeviceContext::State::kStopped ||
        (device_context_->GetState() == CameraDeviceContext::State::kError &&
         !request_manager_)) {
      // In case of Mojo connection error the device may be stopped before
      // StopAndDeAllocate is called; in case of device open failure, the state
      // is set to kError and |request_manager_| is uninitialized.
      std::move(device_close_callback).Run();
      return;
    }
  
    // StopAndDeAllocate may be called at any state except
    // CameraDeviceContext::State::kStopping.
    DCHECK_NE(device_context_->GetState(), CameraDeviceContext::State::kStopping);
  
    device_close_callback_ = std::move(device_close_callback);
    device_context_->SetState(CameraDeviceContext::State::kStopping);
    if (device_ops_.is_bound()) {
      device_ops_->Close(
          base::BindOnce(&CameraDeviceDelegate::OnClosed, GetWeakPtr()));
    }
    // |request_manager_| might be still uninitialized if StopAndDeAllocate() is
    // called before the device is opened.
    if (request_manager_) {
      request_manager_->StopPreview(base::NullCallback());
    }
  }
  
  void CameraDeviceDelegate::TakePhoto(
      VideoCaptureDevice::TakePhotoCallback callback) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    take_photo_callbacks_.push(std::move(callback));
  
    if (!device_context_ ||
        (device_context_->GetState() !=
             CameraDeviceContext::State::kStreamConfigured &&
         device_context_->GetState() != CameraDeviceContext::State::kCapturing)) {
      return;
    }
  
    TakePhotoImpl();
  }
  
  void CameraDeviceDelegate::GetPhotoState(
      VideoCaptureDevice::GetPhotoStateCallback callback) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (result_metadata_frame_number_ <=
        result_metadata_frame_number_for_photo_state_) {
      get_photo_state_queue_.push_back(
          base::BindOnce(&CameraDeviceDelegate::DoGetPhotoState,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
      return;
    }
    DoGetPhotoState(std::move(callback));
  }
  
  // On success, invokes |callback| with value |true|. On failure, drops
  // callback without invoking it.
  //
  // https://www.w3.org/TR/mediacapture-streams/#dfn-applyconstraints-algorithm
  // In a single operation, remove the existing constraints from object, apply
  // newConstraints, and apply successfulSettings as the current settings.
  void CameraDeviceDelegate::SetPhotoOptions(
      mojom::PhotoSettingsPtr settings,
      VideoCaptureDevice::SetPhotoOptionsCallback callback) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (device_context_->GetState() != CameraDeviceContext::State::kCapturing) {
      return;
    }
  
    bool ret = SetPointsOfInterest(settings->points_of_interest);
    if (!ret) {
      LOG(ERROR) << "Failed to set points of interest";
      return;
    }
  
    // Set the vendor tag into with given |name| and |value|. Returns true if
    // the vendor tag is set and false otherwise.
    auto to_uint8_vector = [](int32_t value) {
      std::vector<uint8_t> temp(sizeof(int32_t));
      auto* temp_ptr = reinterpret_cast<int32_t*>(temp.data());
      *temp_ptr = value;
      return temp;
    };
  
    auto set_vendor_int = [&](const std::string& name, bool has_field,
                              double value, bool is_set) {
      const VendorTagInfo* info =
          camera_hal_delegate_->GetVendorTagInfoByName(name);
      if (info == nullptr || !has_field) {
        if (is_set) {
          request_manager_->UnsetRepeatingCaptureMetadata(info->tag);
        }
        return false;
      }
      request_manager_->SetRepeatingCaptureMetadata(info->tag, info->type, 1,
                                                    to_uint8_vector(value));
      return true;
    };
    is_set_brightness_ = set_vendor_int(kBrightness, settings->has_brightness,
                                        settings->brightness, is_set_brightness_);
    is_set_contrast_ = set_vendor_int(kContrast, settings->has_contrast,
                                      settings->contrast, is_set_contrast_);
    is_set_pan_ =
        set_vendor_int(kPan, settings->has_pan, settings->pan, is_set_pan_);
    is_set_saturation_ = set_vendor_int(kSaturation, settings->has_saturation,
                                        settings->saturation, is_set_saturation_);
    is_set_sharpness_ = set_vendor_int(kSharpness, settings->has_sharpness,
                                       settings->sharpness, is_set_sharpness_);
    is_set_tilt_ =
        set_vendor_int(kTilt, settings->has_tilt, settings->tilt, is_set_tilt_);
    if (use_digital_zoom_) {
      if (settings->has_zoom && settings->zoom != 1) {
        double zoom_factor = sqrt(settings->zoom);
        int32_t crop_width = std::round(active_array_size_.width() / zoom_factor);
        int32_t crop_height =
            std::round(active_array_size_.height() / zoom_factor);
        // crop from center
        int32_t region[4] = {(active_array_size_.width() - crop_width) / 2,
                             (active_array_size_.height() - crop_height) / 2,
                             crop_width, crop_height};
  
        request_manager_->SetRepeatingCaptureMetadata(
            cros::mojom::CameraMetadataTag::ANDROID_SCALER_CROP_REGION,
            cros::mojom::EntryType::TYPE_INT32, 4,
            SerializeMetadataValueFromSpan(base::make_span(region, 4)));
  
        VLOG(1) << "zoom ratio:" << settings->zoom << " scaler.crop.region("
                << region[0] << "," << region[1] << "," << region[2] << ","
                << region[3] << ")";
        is_set_zoom_ = true;
      } else if (is_set_zoom_) {
        request_manager_->UnsetRepeatingCaptureMetadata(
            cros::mojom::CameraMetadataTag::ANDROID_SCALER_CROP_REGION);
        is_set_zoom_ = false;
      }
    } else {
      is_set_zoom_ =
          set_vendor_int(kZoom, settings->has_zoom, settings->zoom, is_set_zoom_);
    }
  
    if (settings->has_white_balance_mode &&
        settings->white_balance_mode == mojom::MeteringMode::MANUAL &&
        settings->has_color_temperature) {
      const AwbModeTemperatureMap& map = GetAwbModeTemperatureMap();
      cros::mojom::AndroidControlAwbMode awb_mode =
          cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO;
      auto awb_available_modes = GetMetadataEntryAsSpan<uint8_t>(
          static_metadata_,
          cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES);
      int32_t min_diff = std::numeric_limits<int32_t>::max();
      for (const auto& mode : awb_available_modes) {
        auto it = map.find(mode);
        // Find the nearest awb mode
        int32_t diff = std::abs(settings->color_temperature - it->second);
        if (diff < min_diff) {
          awb_mode = static_cast<cros::mojom::AndroidControlAwbMode>(mode);
          min_diff = diff;
        }
      }
      camera_3a_controller_->SetAutoWhiteBalanceMode(awb_mode);
      is_set_awb_mode_ = true;
    } else if (is_set_awb_mode_) {
      camera_3a_controller_->SetAutoWhiteBalanceMode(
          cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO);
      is_set_awb_mode_ = false;
    }
  
    if (settings->has_exposure_mode &&
        settings->exposure_mode == mojom::MeteringMode::MANUAL &&
        settings->has_exposure_time) {
      int64_t exposure_time_nanoseconds_ =
          settings->exposure_time * 100 * kMicroToNano;
      camera_3a_controller_->SetExposureTime(false, exposure_time_nanoseconds_);
      is_set_exposure_time_ = true;
    } else if (is_set_exposure_time_) {
      camera_3a_controller_->SetExposureTime(true, 0);
      is_set_exposure_time_ = false;
    }
  
    if (settings->has_focus_mode &&
        settings->focus_mode == mojom::MeteringMode::MANUAL &&
        settings->has_focus_distance) {
      // The unit of settings is meter but it is diopter of android metadata.
      float focus_distance_diopters_ = 1.0 / settings->focus_distance;
      camera_3a_controller_->SetFocusDistance(false, focus_distance_diopters_);
      is_set_focus_distance_ = true;
    } else if (is_set_focus_distance_) {
      camera_3a_controller_->SetFocusDistance(true, 0);
      is_set_focus_distance_ = false;
    }
  
    if (settings->has_iso) {
      request_manager_->SetRepeatingCaptureMetadata(
          cros::mojom::CameraMetadataTag::ANDROID_SENSOR_SENSITIVITY,
          cros::mojom::EntryType::TYPE_INT32, 1, to_uint8_vector(settings->iso));
      is_set_iso_ = true;
      if (!is_set_exposure_time_) {
        LOG(WARNING) << "set iso doesn't work due to auto exposure time";
      }
    } else if (is_set_iso_) {
      request_manager_->UnsetRepeatingCaptureMetadata(
          cros::mojom::CameraMetadataTag::ANDROID_SENSOR_SENSITIVITY);
      is_set_iso_ = false;
    }
  
    if (settings->has_exposure_compensation) {
      int metadata_exposure_compensation =
          std::round(settings->exposure_compensation / ae_compensation_step_);
      request_manager_->SetRepeatingCaptureMetadata(
          cros::mojom::CameraMetadataTag::
              ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
          cros::mojom::EntryType::TYPE_INT32, 1,
          to_uint8_vector(metadata_exposure_compensation));
      is_set_exposure_compensation_ = true;
    } else if (is_set_exposure_compensation_) {
      request_manager_->UnsetRepeatingCaptureMetadata(
          cros::mojom::CameraMetadataTag::
              ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION);
      is_set_exposure_compensation_ = false;
    }
  
    // If there is callback of SetPhotoOptions(), the streams might being
    // reconfigured and we should notify them once the reconfiguration is done.
    auto on_reconfigured_callback = base::BindOnce(
        [](VideoCaptureDevice::SetPhotoOptionsCallback callback) {
          std::move(callback).Run(true);
        },
        std::move(callback));
    if (!on_reconfigured_callbacks_.empty()) {
      on_reconfigured_callbacks_.push(std::move(on_reconfigured_callback));
    } else {
      if (MaybeReconfigureForPhotoStream(std::move(settings))) {
        on_reconfigured_callbacks_.push(std::move(on_reconfigured_callback));
      } else {
        std::move(on_reconfigured_callback).Run();
      }
    }
    result_metadata_frame_number_for_photo_state_ = current_request_frame_number_;
  }
  
  void CameraDeviceDelegate::ReconfigureStreams(
      const base::flat_map<ClientType, VideoCaptureParams>& params) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    chrome_capture_params_ = params;
    if (request_manager_) {
      // ReconfigureStreams is used for video recording. It does not require
      // photo.
      request_manager_->StopPreview(base::BindOnce(
          &CameraDeviceDelegate::OnFlushed, GetWeakPtr(), false, base::nullopt));
    }
  }
  
  void CameraDeviceDelegate::SetRotation(int rotation) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    DCHECK(rotation >= 0 && rotation < 360 && rotation % 90 == 0);
    device_context_->SetScreenRotation(rotation);
  }
  
  base::WeakPtr<CameraDeviceDelegate> CameraDeviceDelegate::GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }
  
  bool CameraDeviceDelegate::MaybeReconfigureForPhotoStream(
      mojom::PhotoSettingsPtr settings) {
    bool is_resolution_specified = settings->has_width && settings->has_height;
    bool should_reconfigure_streams =
        (is_resolution_specified && (current_blob_resolution_.IsEmpty() ||
                                     current_blob_resolution_.width() !=
                                         static_cast<int32_t>(settings->width) ||
                                     current_blob_resolution_.height() !=
                                         static_cast<int32_t>(settings->height)));
    if (!should_reconfigure_streams) {
      return false;
    }
  
    if (is_resolution_specified) {
      gfx::Size new_blob_resolution(static_cast<int32_t>(settings->width),
                                    static_cast<int32_t>(settings->height));
      request_manager_->StopPreview(
          base::BindOnce(&CameraDeviceDelegate::OnFlushed, GetWeakPtr(), true,
                         std::move(new_blob_resolution)));
    } else {
      request_manager_->StopPreview(base::BindOnce(
          &CameraDeviceDelegate::OnFlushed, GetWeakPtr(), true, base::nullopt));
    }
    return true;
  }
  
  void CameraDeviceDelegate::TakePhotoImpl() {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    auto construct_request_cb =
        base::BindOnce(&CameraDeviceDelegate::ConstructDefaultRequestSettings,
                       GetWeakPtr(), StreamType::kJpegOutput);
  
    if (request_manager_->HasStreamsConfiguredForTakePhoto()) {
      camera_3a_controller_->Stabilize3AForStillCapture(
          std::move(construct_request_cb));
      return;
    }
  
    // Trigger the reconfigure process if it not yet triggered.
    if (on_reconfigured_callbacks_.empty()) {
      request_manager_->StopPreview(base::BindOnce(
          &CameraDeviceDelegate::OnFlushed, GetWeakPtr(), true, base::nullopt));
    }
    auto on_reconfigured_callback = base::BindOnce(
        [](base::WeakPtr<Camera3AController> controller,
           base::OnceClosure callback) {
          controller->Stabilize3AForStillCapture(std::move(callback));
        },
        camera_3a_controller_->GetWeakPtr(), std::move(construct_request_cb));
    on_reconfigured_callbacks_.push(std::move(on_reconfigured_callback));
  }
  
  void CameraDeviceDelegate::OnMojoConnectionError() {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (device_context_->GetState() == CameraDeviceContext::State::kStopping) {
      // When in stopping state the camera HAL adapter may terminate the Mojo
      // channel before we do, in which case the OnClosed callback will not be
      // called.
      OnClosed(0);
    } else {
      // The Mojo channel terminated unexpectedly.
      if (request_manager_) {
        request_manager_->StopPreview(base::NullCallback());
      }
      device_context_->SetState(CameraDeviceContext::State::kStopped);
      device_context_->SetErrorState(
          media::VideoCaptureError::kCrosHalV3DeviceDelegateMojoConnectionError,
          FROM_HERE, "Mojo connection error");
      ResetMojoInterface();
      // We cannot reset |device_context_| here because
      // |device_context_->SetErrorState| above will call StopAndDeAllocate later
      // to handle the class destruction.
    }
  }
  
  void CameraDeviceDelegate::OnFlushed(
      bool require_photo,
      base::Optional<gfx::Size> new_blob_resolution,
      int32_t result) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    if (result) {
      device_context_->SetErrorState(
          media::VideoCaptureError::kCrosHalV3DeviceDelegateFailedToFlush,
          FROM_HERE,
          std::string("Flush failed: ") + base::safe_strerror(-result));
      return;
    }
    device_context_->SetState(CameraDeviceContext::State::kInitialized);
    ConfigureStreams(require_photo, std::move(new_blob_resolution));
  }
  
  void CameraDeviceDelegate::OnClosed(int32_t result) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    DCHECK_EQ(device_context_->GetState(), CameraDeviceContext::State::kStopping);
  
    device_context_->SetState(CameraDeviceContext::State::kStopped);
    if (result) {
      device_context_->LogToClient(std::string("Failed to close device: ") +
                                   base::safe_strerror(-result));
    }
    if (request_manager_) {
      request_manager_->RemoveResultMetadataObserver(this);
    }
    ResetMojoInterface();
    device_context_ = nullptr;
    current_blob_resolution_.SetSize(0, 0);
    std::move(device_close_callback_).Run();
  }
  
  void CameraDeviceDelegate::ResetMojoInterface() {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    device_ops_.reset();
    camera_3a_controller_.reset();
    request_manager_.reset();
  }
  
  void CameraDeviceDelegate::OnOpenedDevice(int32_t result) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (device_context_ == nullptr) {
      // The OnOpenedDevice() might be called after the camera device already
      // closed by StopAndDeAllocate(), because |camera_hal_delegate_| and
      // |device_ops_| are on different IPC channels. In such case,
      // OnMojoConnectionError() might call OnClosed() and the
      // |device_context_| is cleared, so we just return here.
      return;
    }
  
    if (device_context_->GetState() != CameraDeviceContext::State::kStarting) {
      if (device_context_->GetState() == CameraDeviceContext::State::kError) {
        // In case of camera open failed, the HAL can terminate the Mojo channel
        // before we do and set the state to kError in OnMojoConnectionError.
        return;
      }
      DCHECK_EQ(device_context_->GetState(),
                CameraDeviceContext::State::kStopping);
      OnClosed(0);
      return;
    }
  
    if (result) {
      device_context_->SetErrorState(
          media::VideoCaptureError::
              kCrosHalV3DeviceDelegateFailedToOpenCameraDevice,
          FROM_HERE, "Failed to open camera device");
      return;
    }
    Initialize();
  }
  
  void CameraDeviceDelegate::Initialize() {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    DCHECK_EQ(device_context_->GetState(), CameraDeviceContext::State::kStarting);
  
    mojo::PendingRemote<cros::mojom::Camera3CallbackOps> callback_ops;
    // Assumes the buffer_type will be the same for all |chrome_capture_params|.
    request_manager_ = std::make_unique<RequestManager>(
        callback_ops.InitWithNewPipeAndPassReceiver(),
        std::make_unique<StreamCaptureInterfaceImpl>(GetWeakPtr()),
        device_context_,
        chrome_capture_params_[ClientType::kPreviewClient].buffer_type,
        std::make_unique<CameraBufferFactory>(),
        base::BindRepeating(&RotateAndBlobify), ipc_task_runner_,
        camera_app_device_);
    camera_3a_controller_ = std::make_unique<Camera3AController>(
        static_metadata_, request_manager_.get(), ipc_task_runner_);
    device_ops_->Initialize(
        std::move(callback_ops),
        base::BindOnce(&CameraDeviceDelegate::OnInitialized, GetWeakPtr()));
    request_manager_->AddResultMetadataObserver(this);
  }
  
  void CameraDeviceDelegate::OnInitialized(int32_t result) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (device_context_->GetState() != CameraDeviceContext::State::kStarting) {
      DCHECK_EQ(device_context_->GetState(),
                CameraDeviceContext::State::kStopping);
      return;
    }
    if (result) {
      device_context_->SetErrorState(
          media::VideoCaptureError::
              kCrosHalV3DeviceDelegateFailedToInitializeCameraDevice,
          FROM_HERE,
          std::string("Failed to initialize camera device: ") +
              base::safe_strerror(-result));
      return;
    }
    device_context_->SetState(CameraDeviceContext::State::kInitialized);
    bool require_photo = [&] {
      if (!camera_app_device_) {
        return false;
      }
      auto capture_intent = camera_app_device_->GetCaptureIntent();
      switch (capture_intent) {
        case cros::mojom::CaptureIntent::DEFAULT:
          return false;
        case cros::mojom::CaptureIntent::STILL_CAPTURE:
          return true;
        case cros::mojom::CaptureIntent::VIDEO_RECORD:
          return false;
        default:
          NOTREACHED() << "Unknown capture intent: " << capture_intent;
          return false;
      }
    }();
    ConfigureStreams(require_photo, base::nullopt);
  }
  
  void CameraDeviceDelegate::ConfigureStreams(
      bool require_photo,
      base::Optional<gfx::Size> new_blob_resolution) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    DCHECK_EQ(device_context_->GetState(),
              CameraDeviceContext::State::kInitialized);
  
    cros::mojom::Camera3StreamConfigurationPtr stream_config =
        cros::mojom::Camera3StreamConfiguration::New();
    for (const auto& param : chrome_capture_params_) {
      // Set up context for preview stream and record stream.
      cros::mojom::Camera3StreamPtr stream = cros::mojom::Camera3Stream::New();
      StreamType stream_type = (param.first == ClientType::kPreviewClient)
                                   ? StreamType::kPreviewOutput
                                   : StreamType::kRecordingOutput;
      // TODO(henryhsu): PreviewClient should remove HW_VIDEO_ENCODER usage when
      // multiple streams enabled.
      auto usage = (param.first == ClientType::kPreviewClient)
                       ? (cros::mojom::GRALLOC_USAGE_HW_COMPOSER |
                          cros::mojom::GRALLOC_USAGE_HW_VIDEO_ENCODER)
                       : cros::mojom::GRALLOC_USAGE_HW_VIDEO_ENCODER;
      stream->id = static_cast<uint64_t>(stream_type);
      stream->stream_type = cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
      stream->width = param.second.requested_format.frame_size.width();
      stream->height = param.second.requested_format.frame_size.height();
      stream->format =
          cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888;
      stream->usage = usage;
      stream->data_space = 0;
      stream->rotation =
          cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
  
      stream_config->streams.push_back(std::move(stream));
    }
  
    // Set up context for still capture stream. We set still capture stream to the
    // JPEG stream configuration with maximum supported resolution.
    int32_t blob_width = 0;
    int32_t blob_height = 0;
    if (require_photo) {
      auto blob_resolution = GetBlobResolution(new_blob_resolution);
      if (blob_resolution.IsEmpty()) {
        LOG(ERROR) << "Failed to configure streans: No BLOB resolution found.";
        return;
      }
  
      blob_width = blob_resolution.width();
      blob_height = blob_resolution.height();
  
      cros::mojom::Camera3StreamPtr still_capture_stream =
          cros::mojom::Camera3Stream::New();
      still_capture_stream->id = static_cast<uint64_t>(StreamType::kJpegOutput);
      still_capture_stream->stream_type =
          cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
      still_capture_stream->width = blob_width;
      still_capture_stream->height = blob_height;
      still_capture_stream->format =
          cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB;
      // Set usage flag to allow HAL adapter to identify a still capture stream.
      still_capture_stream->usage = cros::mojom::GRALLOC_USAGE_STILL_CAPTURE;
      still_capture_stream->data_space = 0;
      still_capture_stream->rotation =
          cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
      stream_config->streams.push_back(std::move(still_capture_stream));
  
      int32_t max_yuv_width = 0, max_yuv_height = 0;
      if (IsYUVReprocessingSupported(&max_yuv_width, &max_yuv_height)) {
        auto reprocessing_stream_input = cros::mojom::Camera3Stream::New();
        reprocessing_stream_input->id =
            static_cast<uint64_t>(StreamType::kYUVInput);
        reprocessing_stream_input->stream_type =
            cros::mojom::Camera3StreamType::CAMERA3_STREAM_INPUT;
        reprocessing_stream_input->width = max_yuv_width;
        reprocessing_stream_input->height = max_yuv_height;
        reprocessing_stream_input->format =
            cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888;
        reprocessing_stream_input->data_space = 0;
        reprocessing_stream_input->rotation =
            cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
  
        auto reprocessing_stream_output = cros::mojom::Camera3Stream::New();
        reprocessing_stream_output->id =
            static_cast<uint64_t>(StreamType::kYUVOutput);
        reprocessing_stream_output->stream_type =
            cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
        reprocessing_stream_output->width = max_yuv_width;
        reprocessing_stream_output->height = max_yuv_height;
        reprocessing_stream_output->format =
            cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888;
        // Set usage flag to allow HAL adapter to identify a still capture stream.
        reprocessing_stream_output->usage =
            cros::mojom::GRALLOC_USAGE_STILL_CAPTURE;
        reprocessing_stream_output->data_space = 0;
        reprocessing_stream_output->rotation =
            cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
  
        stream_config->streams.push_back(std::move(reprocessing_stream_input));
        stream_config->streams.push_back(std::move(reprocessing_stream_output));
      }
    }
  
    stream_config->operation_mode = cros::mojom::Camera3StreamConfigurationMode::
        CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE;
    device_ops_->ConfigureStreams(
        std::move(stream_config),
        base::BindOnce(&CameraDeviceDelegate::OnConfiguredStreams, GetWeakPtr(),
                       gfx::Size(blob_width, blob_height)));
  }
  
  void CameraDeviceDelegate::OnConfiguredStreams(
      gfx::Size blob_resolution,
      int32_t result,
      cros::mojom::Camera3StreamConfigurationPtr updated_config) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (device_context_->GetState() != CameraDeviceContext::State::kInitialized) {
      DCHECK_EQ(device_context_->GetState(),
                CameraDeviceContext::State::kStopping);
      return;
    }
    if (result) {
      device_context_->SetErrorState(
          media::VideoCaptureError::
              kCrosHalV3DeviceDelegateFailedToConfigureStreams,
          FROM_HERE,
          std::string("Failed to configure streams: ") +
              base::safe_strerror(-result));
      return;
    }
    if (!updated_config ||
        updated_config->streams.size() > kMaxConfiguredStreams ||
        updated_config->streams.size() < kMinConfiguredStreams) {
      device_context_->SetErrorState(
          media::VideoCaptureError::
              kCrosHalV3DeviceDelegateWrongNumberOfStreamsConfigured,
          FROM_HERE,
          std::string("Wrong number of streams configured: ") +
              base::NumberToString(updated_config->streams.size()));
      return;
    }
  
    current_blob_resolution_.SetSize(blob_resolution.width(),
                                     blob_resolution.height());
  
    request_manager_->SetUpStreamsAndBuffers(chrome_capture_params_,
                                             static_metadata_,
                                             std::move(updated_config->streams));
  
    device_context_->SetState(CameraDeviceContext::State::kStreamConfigured);
    // Kick off the preview stream.
    ConstructDefaultRequestSettings(StreamType::kPreviewOutput);
  }
  
  bool CameraDeviceDelegate::IsYUVReprocessingSupported(int* max_width,
                                                        int* max_height) {
    bool has_yuv_reprocessing_capability = [&] {
      auto capabilities = GetMetadataEntryAsSpan<uint8_t>(
          static_metadata_,
          cros::mojom::CameraMetadataTag::ANDROID_REQUEST_AVAILABLE_CAPABILITIES);
      auto capability_yuv_reprocessing = static_cast<uint8_t>(
          cros::mojom::AndroidRequestAvailableCapabilities::
              ANDROID_REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
      for (auto capability : capabilities) {
        if (capability == capability_yuv_reprocessing) {
          return true;
        }
      }
      return false;
    }();
  
    if (!has_yuv_reprocessing_capability) {
      return false;
    }
  
    bool has_yuv_input_blob_output = [&] {
      auto formats_map = GetMetadataEntryAsSpan<int32_t>(
          static_metadata_,
          cros::mojom::CameraMetadataTag::
              ANDROID_SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP);
      // The formats map looks like: [
      //   {INPUT_FORMAT, NUM_OF_OUTPUTS, OUTPUT_FORMAT_1, OUTPUT_FORMAT_2, ...},
      //   {...},
      //   ...
      // ]
      auto format_yuv = static_cast<int32_t>(
          cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888);
      auto format_blob = static_cast<int32_t>(
          cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB);
  
      size_t idx = 0;
      while (idx < formats_map.size()) {
        auto in_format = formats_map[idx++];
        auto out_amount = formats_map[idx++];
        if (in_format != format_yuv) {
          idx += out_amount;
          continue;
        }
        for (size_t idx_end = idx + out_amount; idx < idx_end; idx++) {
          auto out_format = formats_map[idx];
          if (out_format == format_blob) {
            return true;
          }
        }
      }
      return false;
    }();
  
    if (!has_yuv_input_blob_output) {
      return false;
    }
  
    std::vector<gfx::Size> yuv_resolutions;
    GetStreamResolutions(
        static_metadata_, cros::mojom::Camera3StreamType::CAMERA3_STREAM_INPUT,
        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888,
        &yuv_resolutions);
    if (yuv_resolutions.empty()) {
      return false;
    }
    *max_width = yuv_resolutions.back().width();
    *max_height = yuv_resolutions.back().height();
    return true;
  }
  
  void CameraDeviceDelegate::ConstructDefaultRequestSettings(
      StreamType stream_type) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    DCHECK(device_context_->GetState() ==
               CameraDeviceContext::State::kStreamConfigured ||
           device_context_->GetState() == CameraDeviceContext::State::kCapturing);
  
    if (stream_type == StreamType::kPreviewOutput) {
      // CCA uses the same stream for preview and video recording. Choose proper
      // template here so the underlying camera HAL can set 3A tuning accordingly.
      auto request_template =
          camera_app_device_ && camera_app_device_->GetCaptureIntent() ==
                                    cros::mojom::CaptureIntent::VIDEO_RECORD
              ? cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_VIDEO_RECORD
              : cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW;
      device_ops_->ConstructDefaultRequestSettings(
          request_template,
          base::BindOnce(
              &CameraDeviceDelegate::OnConstructedDefaultPreviewRequestSettings,
              GetWeakPtr()));
    } else if (stream_type == StreamType::kJpegOutput) {
      device_ops_->ConstructDefaultRequestSettings(
          cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_STILL_CAPTURE,
          base::BindOnce(&CameraDeviceDelegate::
                             OnConstructedDefaultStillCaptureRequestSettings,
                         GetWeakPtr()));
    } else {
      NOTREACHED() << "No default request settings for stream: " << stream_type;
    }
  }
  
  void CameraDeviceDelegate::OnConstructedDefaultPreviewRequestSettings(
      cros::mojom::CameraMetadataPtr settings) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    if (device_context_->GetState() !=
        CameraDeviceContext::State::kStreamConfigured) {
      DCHECK_EQ(device_context_->GetState(),
                CameraDeviceContext::State::kStopping);
      return;
    }
    if (!settings) {
      device_context_->SetErrorState(
          media::VideoCaptureError::
              kCrosHalV3DeviceDelegateFailedToGetDefaultRequestSettings,
          FROM_HERE, "Failed to get default request settings");
      return;
    }
  
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    device_context_->SetState(CameraDeviceContext::State::kCapturing);
    camera_3a_controller_->SetAutoFocusModeForStillCapture();
  
    auto specified_fps_range =
        camera_app_device_ ? camera_app_device_->GetFpsRange() : base::nullopt;
    if (specified_fps_range) {
      SetFpsRangeInMetadata(&settings, specified_fps_range->GetMin(),
                            specified_fps_range->GetMax());
    } else {
      // Assumes the frame_rate will be the same for all |chrome_capture_params|.
      int32_t requested_frame_rate =
          std::round(chrome_capture_params_[ClientType::kPreviewClient]
                         .requested_format.frame_rate);
      bool prefer_constant_frame_rate =
          base::FeatureList::IsEnabled(
              chromeos::features::kPreferConstantFrameRate) ||
          (camera_app_device_ && camera_app_device_->GetCaptureIntent() ==
                                     cros::mojom::CaptureIntent::VIDEO_RECORD);
      int32_t target_min, target_max;
      std::tie(target_min, target_max) = GetTargetFrameRateRange(
          static_metadata_, requested_frame_rate, prefer_constant_frame_rate);
      if (target_min == 0 || target_max == 0) {
        device_context_->SetErrorState(
            media::VideoCaptureError::
                kCrosHalV3DeviceDelegateFailedToGetDefaultRequestSettings,
            FROM_HERE, "Failed to get valid frame rate range");
        return;
      }
  
      SetFpsRangeInMetadata(&settings, target_min, target_max);
    }
    request_manager_->StartPreview(std::move(settings));
  
    if (!take_photo_callbacks_.empty()) {
      TakePhotoImpl();
    }
  
    while (!on_reconfigured_callbacks_.empty()) {
      std::move(on_reconfigured_callbacks_.front()).Run();
      on_reconfigured_callbacks_.pop();
    }
  }
  
  void CameraDeviceDelegate::OnConstructedDefaultStillCaptureRequestSettings(
      cros::mojom::CameraMetadataPtr settings) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    while (!take_photo_callbacks_.empty()) {
      auto take_photo_callback = base::BindOnce(
          &TakePhotoCallbackBundle, std::move(take_photo_callbacks_.front()),
          base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
                         camera_3a_controller_->GetWeakPtr()));
      if (camera_app_device_) {
        camera_app_device_->ConsumeReprocessOptions(
            std::move(take_photo_callback),
            media::BindToCurrentLoop(base::BindOnce(
                &RequestManager::TakePhoto, request_manager_->GetWeakPtr(),
                settings.Clone())));
      } else {
        request_manager_->TakePhoto(
            settings.Clone(), CameraAppDeviceImpl::GetSingleShotReprocessOptions(
                                  std::move(take_photo_callback)));
      }
      take_photo_callbacks_.pop();
    }
  }
  
  gfx::Size CameraDeviceDelegate::GetBlobResolution(
      base::Optional<gfx::Size> new_blob_resolution) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    std::vector<gfx::Size> blob_resolutions;
    GetStreamResolutions(
        static_metadata_, cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT,
        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB, &blob_resolutions);
    if (blob_resolutions.empty()) {
      return {};
    }
  
    // Try the given blob resolution first. If it is invalid, try the
    // resolution specified through Mojo API as a fallback. If it fails too,
    // use the largest resolution as default.
    if (new_blob_resolution.has_value() &&
        base::Contains(blob_resolutions, *new_blob_resolution)) {
      return *new_blob_resolution;
    }
  
    if (camera_app_device_) {
      auto specified_capture_resolution =
          camera_app_device_->GetStillCaptureResolution();
      if (!specified_capture_resolution.IsEmpty() &&
          base::Contains(blob_resolutions, specified_capture_resolution)) {
        return specified_capture_resolution;
      }
    }
    return blob_resolutions.back();
  }
  
  void CameraDeviceDelegate::ProcessCaptureRequest(
      cros::mojom::Camera3CaptureRequestPtr request,
      base::OnceCallback<void(int32_t)> callback) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    for (const auto& output_buffer : request->output_buffers) {
      TRACE_EVENT2("camera", "Capture Request", "frame_number",
                   request->frame_number, "stream_id", output_buffer->stream_id);
    }
    current_request_frame_number_ = request->frame_number;
  
    if (device_context_->GetState() != CameraDeviceContext::State::kCapturing) {
      DCHECK_EQ(device_context_->GetState(),
                CameraDeviceContext::State::kStopping);
      return;
    }
    device_ops_->ProcessCaptureRequest(std::move(request), std::move(callback));
  }
  
  void CameraDeviceDelegate::Flush(base::OnceCallback<void(int32_t)> callback) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    device_ops_->Flush(std::move(callback));
  }
  
  bool CameraDeviceDelegate::SetPointsOfInterest(
      const std::vector<mojom::Point2DPtr>& points_of_interest) {
    if (points_of_interest.empty()) {
      return true;
    }
  
    if (!camera_3a_controller_->IsPointOfInterestSupported()) {
      LOG(WARNING) << "Point of interest is not supported on this device.";
      return false;
    }
  
    if (points_of_interest.size() > 1) {
      LOG(WARNING) << "Setting more than one point of interest is not "
                      "supported, only the first one is used.";
    }
  
    // A Point2D Point of Interest is interpreted to represent a pixel position in
    // a normalized square space (|{x,y} ∈ [0.0, 1.0]|). The origin of coordinates
    // |{x,y} = {0.0, 0.0}| represents the upper leftmost corner whereas the
    // |{x,y} = {1.0, 1.0}| represents the lower rightmost corner: the x
    // coordinate (columns) increases rightwards and the y coordinate (rows)
    // increases downwards. Values beyond the mentioned limits will be clamped to
    // the closest allowed value.
    // ref: https://www.w3.org/TR/image-capture/#points-of-interest
  
    double x = base::ClampToRange(points_of_interest[0]->x, 0.0, 1.0);
    double y = base::ClampToRange(points_of_interest[0]->y, 0.0, 1.0);
  
    // Handle rotation, still in normalized square space.
    std::tie(x, y) = [&]() -> std::pair<double, double> {
      switch (device_context_->GetCameraFrameRotation()) {
        case 0:
          return {x, y};
        case 90:
          return {y, 1.0 - x};
        case 180:
          return {1.0 - x, 1.0 - y};
        case 270:
          return {1.0 - y, x};
        default:
          NOTREACHED() << "Invalid orientation";
      }
      return {x, y};
    }();
  
    // TODO(shik): Respect to SCALER_CROP_REGION, which is unused now.
    x *= active_array_size_.width() - 1;
    y *= active_array_size_.height() - 1;
    gfx::Point point = {static_cast<int>(x), static_cast<int>(y)};
    camera_3a_controller_->SetPointOfInterest(point);
    return true;
  }
  
  mojom::RangePtr CameraDeviceDelegate::GetControlRangeByVendorTagName(
      const std::string& range_name,
      const base::Optional<int32_t>& current) {
    const VendorTagInfo* info =
        camera_hal_delegate_->GetVendorTagInfoByName(range_name);
    if (info == nullptr) {
      return mojom::Range::New();
    }
    auto static_val =
        GetMetadataEntryAsSpan<int32_t>(static_metadata_, info->tag);
    if (static_val.size() != 3) {
      return mojom::Range::New();
    }
  
    if (!current) {
      return mojom::Range::New();
    }
  
    mojom::RangePtr range = mojom::Range::New();
  
    range->min = static_val[0];
    range->max = static_val[1];
    range->step = static_val[2];
    range->current = current.value();
  
    return range;
  }
  
  void CameraDeviceDelegate::OnResultMetadataAvailable(
      uint32_t frame_number,
      const cros::mojom::CameraMetadataPtr& result_metadata) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    auto get_vendor_int =
        [&](const std::string& name,
            const cros::mojom::CameraMetadataPtr& result_metadata,
            base::Optional<int32_t>* returned_value) {
          returned_value->reset();
          const VendorTagInfo* info =
              camera_hal_delegate_->GetVendorTagInfoByName(name);
          if (info == nullptr)
            return;
          auto val = GetMetadataEntryAsSpan<int32_t>(result_metadata, info->tag);
          if (val.size() == 1)
            *returned_value = val[0];
        };
  
    get_vendor_int(kBrightness, result_metadata, &result_metadata_.brightness);
    get_vendor_int(kContrast, result_metadata, &result_metadata_.contrast);
    get_vendor_int(kPan, result_metadata, &result_metadata_.pan);
    get_vendor_int(kSaturation, result_metadata, &result_metadata_.saturation);
    get_vendor_int(kSharpness, result_metadata, &result_metadata_.sharpness);
    get_vendor_int(kTilt, result_metadata, &result_metadata_.tilt);
    get_vendor_int(kZoom, result_metadata, &result_metadata_.zoom);
  
    result_metadata_.scaler_crop_region.reset();
    auto rect = GetMetadataEntryAsSpan<int32_t>(
        result_metadata,
        cros::mojom::CameraMetadataTag::ANDROID_SCALER_CROP_REGION);
    if (rect.size() == 4) {
      result_metadata_.scaler_crop_region =
          gfx::Rect(rect[0], rect[1], rect[2], rect[3]);
    }
  
    result_metadata_.ae_mode.reset();
    auto ae_mode = GetMetadataEntryAsSpan<uint8_t>(
        result_metadata, cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE);
    if (ae_mode.size() == 1)
      result_metadata_.ae_mode = ae_mode[0];
  
    result_metadata_.exposure_time.reset();
    auto exposure_time = GetMetadataEntryAsSpan<int64_t>(
        result_metadata,
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_EXPOSURE_TIME);
    if (exposure_time.size() == 1)
      result_metadata_.exposure_time = exposure_time[0];
  
    result_metadata_.awb_mode.reset();
    auto awb_mode = GetMetadataEntryAsSpan<uint8_t>(
        result_metadata,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE);
    if (awb_mode.size() == 1)
      result_metadata_.awb_mode = awb_mode[0];
  
    result_metadata_.af_mode.reset();
    auto af_mode = GetMetadataEntryAsSpan<uint8_t>(
        result_metadata, cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE);
    if (af_mode.size() == 1)
      result_metadata_.af_mode = af_mode[0];
  
    result_metadata_.focus_distance.reset();
    auto focus_distance = GetMetadataEntryAsSpan<float>(
        result_metadata,
        cros::mojom::CameraMetadataTag::ANDROID_LENS_FOCUS_DISTANCE);
    if (focus_distance.size() == 1)
      result_metadata_.focus_distance = focus_distance[0];
  
    result_metadata_.sensitivity.reset();
    auto sensitivity = GetMetadataEntryAsSpan<int32_t>(
        result_metadata,
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_SENSITIVITY);
    if (sensitivity.size() == 1)
      result_metadata_.sensitivity = sensitivity[0];
  
    result_metadata_.ae_compensation.reset();
    auto ae_compensation = GetMetadataEntryAsSpan<int32_t>(
        result_metadata,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION);
    if (ae_compensation.size() == 1)
      result_metadata_.ae_compensation = ae_compensation[0];
  
    result_metadata_frame_number_ = frame_number;
    // We need to wait the new result metadata for new settings.
    if (result_metadata_frame_number_ >
        result_metadata_frame_number_for_photo_state_) {
      for (auto& request : get_photo_state_queue_)
        ipc_task_runner_->PostTask(FROM_HERE, std::move(request));
      get_photo_state_queue_.clear();
    }
  }
  
  void CameraDeviceDelegate::DoGetPhotoState(
      VideoCaptureDevice::GetPhotoStateCallback callback) {
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
  
    auto photo_state = mojo::CreateEmptyPhotoState();
  
    if (!device_context_ ||
        (device_context_->GetState() !=
             CameraDeviceContext::State::kStreamConfigured &&
         device_context_->GetState() != CameraDeviceContext::State::kCapturing)) {
      std::move(callback).Run(std::move(photo_state));
      return;
    }
  
    std::vector<gfx::Size> blob_resolutions;
    GetStreamResolutions(
        static_metadata_, cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT,
        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB, &blob_resolutions);
    if (blob_resolutions.empty()) {
      std::move(callback).Run(std::move(photo_state));
      return;
    }
  
    // Sets the correct range of min/max resolution in order to bypass checks that
    // the resolution caller request should fall within the range when taking
    // photos. And since we are not actually use the mechanism to get other
    // resolutions, we set the step to 0.0 here.
    photo_state->width->current = current_blob_resolution_.width();
    photo_state->width->min = blob_resolutions.front().width();
    photo_state->width->max = blob_resolutions.back().width();
    photo_state->width->step = 0.0;
    photo_state->height->current = current_blob_resolution_.height();
    photo_state->height->min = blob_resolutions.front().height();
    photo_state->height->max = blob_resolutions.back().height();
    photo_state->height->step = 0.0;
  
    photo_state->brightness = GetControlRangeByVendorTagName(
        kBrightnessRange, result_metadata_.brightness);
    photo_state->contrast =
        GetControlRangeByVendorTagName(kContrastRange, result_metadata_.contrast);
    photo_state->pan =
        GetControlRangeByVendorTagName(kPanRange, result_metadata_.pan);
    photo_state->saturation = GetControlRangeByVendorTagName(
        kSaturationRange, result_metadata_.saturation);
    photo_state->sharpness = GetControlRangeByVendorTagName(
        kSharpnessRange, result_metadata_.sharpness);
    photo_state->tilt =
        GetControlRangeByVendorTagName(kTiltRange, result_metadata_.tilt);
  
    // For zoom part, we check the scaler.availableMaxDigitalZoom first, if there
    // is no metadata or the value is one we use zoom vendor tag.
    //
    // https://w3c.github.io/mediacapture-image/#zoom
    //
    // scaler.availableMaxDigitalZoom:
    // We use area ratio for this type zoom.
    //
    // Vendor tag zoom:
    // It is used by UVC camera usually.
    // The zoom unit is driver-specific for V4L2_CID_ZOOM_ABSOLUTE.
    // https://www.kernel.org/doc/html/latest/media/uapi/v4l/ext-ctrls-camera.html
    auto max_digital_zoom = GetMetadataEntryAsSpan<float>(
        static_metadata_, cros::mojom::CameraMetadataTag::
                              ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
    if (max_digital_zoom.size() == 1 && max_digital_zoom[0] > 1 &&
        result_metadata_.scaler_crop_region) {
      photo_state->zoom->min = 1;
      photo_state->zoom->max = max_digital_zoom[0] * max_digital_zoom[0];
      photo_state->zoom->step = 0.1;
      photo_state->zoom->current =
          (active_array_size_.width() /
           (float)result_metadata_.scaler_crop_region->width()) *
          (active_array_size_.height() /
           (float)result_metadata_.scaler_crop_region->height());
      // get 0.1 precision
      photo_state->zoom->current = round(photo_state->zoom->current * 10) / 10;
      use_digital_zoom_ = true;
    } else {
      photo_state->zoom =
          GetControlRangeByVendorTagName(kZoomRange, result_metadata_.zoom);
      use_digital_zoom_ = false;
    }
  
    auto awb_available_modes = GetMetadataEntryAsSpan<uint8_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES);
  
    // Only enable white balance control when there are more than 1 control modes.
    if (awb_available_modes.size() > 1 && result_metadata_.awb_mode) {
      photo_state->supported_white_balance_modes.push_back(
          mojom::MeteringMode::MANUAL);
      photo_state->supported_white_balance_modes.push_back(
          mojom::MeteringMode::CONTINUOUS);
      const AwbModeTemperatureMap& map = GetAwbModeTemperatureMap();
      int32_t current_temperature = 0;
      if (result_metadata_.awb_mode ==
          static_cast<uint8_t>(
              cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO))
        photo_state->current_white_balance_mode = mojom::MeteringMode::CONTINUOUS;
      else {
        // Need to find current color temperature.
        photo_state->current_white_balance_mode = mojom::MeteringMode::MANUAL;
        current_temperature = map.at(result_metadata_.awb_mode.value());
      }
  
      int32_t min = std::numeric_limits<int32_t>::max();
      int32_t max = std::numeric_limits<int32_t>::min();
      for (const auto& mode : awb_available_modes) {
        auto it = map.find(mode);
        if (it == map.end())
          continue;
        if (it->second < min)
          min = it->second;
        else if (it->second > max)
          max = it->second;
      }
      photo_state->color_temperature->min = min;
      photo_state->color_temperature->max = max;
      photo_state->color_temperature->step = kColorTemperatureStep;
      photo_state->color_temperature->current = current_temperature;
    }
  
    auto ae_available_modes = GetMetadataEntryAsSpan<uint8_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_AVAILABLE_MODES);
    bool support_manual_exposure_time = false;
    if (ae_available_modes.size() > 1 && result_metadata_.ae_mode) {
      support_manual_exposure_time = base::Contains(
          ae_available_modes,
          static_cast<uint8_t>(
              cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF));
    }
  
    auto exposure_time_range = GetMetadataEntryAsSpan<int64_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE);
  
    if (support_manual_exposure_time && exposure_time_range.size() == 2 &&
        result_metadata_.exposure_time) {
      photo_state->supported_exposure_modes.push_back(
          mojom::MeteringMode::MANUAL);
      photo_state->supported_exposure_modes.push_back(
          mojom::MeteringMode::CONTINUOUS);
      if (result_metadata_.ae_mode ==
          static_cast<uint8_t>(
              cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF))
        photo_state->current_exposure_mode = mojom::MeteringMode::MANUAL;
      else
        photo_state->current_exposure_mode = mojom::MeteringMode::CONTINUOUS;
  
      // The unit of photo_state->exposure_time is 100 microseconds and from
      // metadata is nanoseconds.
      photo_state->exposure_time->min = std::ceil(
          static_cast<float>(exposure_time_range[0]) / (100 * kMicroToNano));
      photo_state->exposure_time->max =
          exposure_time_range[1] / (100 * kMicroToNano);
      photo_state->exposure_time->step = 1;  // 100 microseconds
      photo_state->exposure_time->current =
          result_metadata_.exposure_time.value() / (100 * kMicroToNano);
    }
  
    auto af_available_modes = GetMetadataEntryAsSpan<uint8_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES);
    bool support_manual_focus_distance = false;
    if (af_available_modes.size() > 1 && result_metadata_.af_mode) {
      support_manual_focus_distance = base::Contains(
          af_available_modes,
          static_cast<uint8_t>(
              cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF));
    }
  
    auto minimum_focus_distance = GetMetadataEntryAsSpan<float>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE);
    // If the lens is fixed-focus, minimum_focus_distance will be 0.
    if (support_manual_focus_distance && minimum_focus_distance.size() == 1 &&
        minimum_focus_distance[0] != 0 && result_metadata_.focus_distance) {
      photo_state->supported_focus_modes.push_back(mojom::MeteringMode::MANUAL);
      photo_state->supported_focus_modes.push_back(
          mojom::MeteringMode::CONTINUOUS);
      if (result_metadata_.af_mode ==
          static_cast<uint8_t>(
              cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF))
        photo_state->current_focus_mode = mojom::MeteringMode::MANUAL;
      else
        photo_state->current_focus_mode = mojom::MeteringMode::CONTINUOUS;
  
      // The unit of photo_state->focus_distance is meter and from metadata is
      // diopter.
      photo_state->focus_distance->min =
          std::roundf(100.0 / minimum_focus_distance[0]) / 100.0;
      photo_state->focus_distance->max = std::numeric_limits<double>::infinity();
      photo_state->focus_distance->step = 0.01;
      if (result_metadata_.focus_distance.value() == 0) {
        photo_state->focus_distance->current =
            std::numeric_limits<double>::infinity();
      } else {
        // We want to make sure |current| is a possible value of
        // |min| + |steps(0.01)|*X.  The minimum can be divided by step(0.01). So
        // we only need to round the value less than 0.01.
        double meters = 1.0 / result_metadata_.focus_distance.value();
        photo_state->focus_distance->current = std::roundf(meters * 100) / 100.0;
      }
    }
  
    auto sensitivity_range = GetMetadataEntryAsSpan<int32_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_SENSITIVITY_RANGE);
    if (sensitivity_range.size() == 2 && result_metadata_.sensitivity) {
      photo_state->iso->min = sensitivity_range[0];
      photo_state->iso->max = sensitivity_range[1];
      photo_state->iso->step = 1;
      photo_state->iso->current = result_metadata_.sensitivity.value();
    }
  
    auto ae_compensation_range = GetMetadataEntryAsSpan<int32_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_COMPENSATION_RANGE);
    ae_compensation_step_ = 0.0;
    if (ae_compensation_range.size() == 2) {
      if (ae_compensation_range[0] != 0 || ae_compensation_range[1] != 0) {
        auto ae_compensation_step = GetMetadataEntryAsSpan<Rational>(
            static_metadata_,
            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_COMPENSATION_STEP);
        if (ae_compensation_step.size() == 1) {
          if (ae_compensation_step[0].numerator == 0 ||
              ae_compensation_step[0].denominator == 0) {
            LOG(WARNING) << "AE_COMPENSATION_STEP: numerator:"
                         << ae_compensation_step[0].numerator << ", denominator:"
                         << ae_compensation_step[0].denominator;
          } else {
            ae_compensation_step_ =
                static_cast<float>(ae_compensation_step[0].numerator) /
                static_cast<float>(ae_compensation_step[0].denominator);
            photo_state->exposure_compensation->min =
                ae_compensation_range[0] * ae_compensation_step_;
            photo_state->exposure_compensation->max =
                ae_compensation_range[1] * ae_compensation_step_;
            photo_state->exposure_compensation->step = ae_compensation_step_;
            if (result_metadata_.ae_compensation)
              photo_state->exposure_compensation->current =
                  result_metadata_.ae_compensation.value() *
                  ae_compensation_step_;
            else
              photo_state->exposure_compensation->current = 0;
          }
        }
      }
    }
  
    std::move(callback).Run(std::move(photo_state));
  }
  
  }  // namespace media
  