chromium/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc

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

#include "chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <string>

#include "ash/constants/ash_features.h"
#include "ash/controls/contextual_tooltip.h"
#include "ash/public/cpp/image_util.h"
#include "ash/shell.h"
#include "ash/system/camera/camera_effects_controller.h"
#include "ash/wallpaper/sea_pen_wallpaper_manager.h"
#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.h"
#include "chrome/browser/ash/wallpaper_handlers/wallpaper_fetcher_delegate.h"
#include "chrome/browser/ui/webui/sanitized_image_source.h"
#include "components/manta/features.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_ui.h"
#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
#include "media/capture/video/chromeos/mojom/effects_pipeline.mojom-forward.h"
#include "ui/gfx/image/image_skia.h"

namespace ash::vc_background_ui {

namespace {

CameraEffectsController* GetCameraEffectsController() {
  auto* controller = Shell::Get()->camera_effects_controller();
  DCHECK(controller);
  return controller;
}

void OnGetBackgroundImageInfo(
    SeaPenWallpaperManager::GetImageAndMetadataCallback callback,
    const std::optional<CameraEffectsController::BackgroundImageInfo>&
        background_image_info) {
  if (!background_image_info.has_value()) {
    std::move(callback).Run(gfx::ImageSkia(), nullptr);
    return;
  }

  const std::string extracted_metadata =
      ExtractDcDescriptionContents(background_image_info->metadata);
  DecodeJsonMetadata(
      extracted_metadata.empty() ? background_image_info->metadata
                                 : extracted_metadata,
      base::BindOnce(std::move(callback), background_image_info->image));
}

}  // namespace

VcBackgroundUISeaPenProviderImpl::VcBackgroundUISeaPenProviderImpl(
    content::WebUI* web_ui,
    std::unique_ptr<wallpaper_handlers::WallpaperFetcherDelegate>
        wallpaper_fetcher_delegate)
    : PersonalizationAppSeaPenProviderBase(
          web_ui,
          std::move(wallpaper_fetcher_delegate),
          manta::proto::FeatureName::CHROMEOS_VC_BACKGROUNDS) {
  content::URLDataSource::Add(profile_,
                              std::make_unique<SanitizedImageSource>(profile_));
}

VcBackgroundUISeaPenProviderImpl::~VcBackgroundUISeaPenProviderImpl() = default;

void VcBackgroundUISeaPenProviderImpl::BindInterface(
    mojo::PendingReceiver<ash::personalization_app::mojom::SeaPenProvider>
        receiver) {
  CHECK(::ash::features::IsVcBackgroundReplaceEnabled());
  CHECK(::manta::features::IsMantaServiceEnabled());
  ::ash::personalization_app::PersonalizationAppSeaPenProviderBase::
      BindInterface(std::move(receiver));
}

void VcBackgroundUISeaPenProviderImpl::SetSeaPenObserverInternal() {
  if (!scoped_camera_effect_observation_.IsObserving()) {
    scoped_camera_effect_observation_.Observe(
        media::CameraHalDispatcherImpl::GetInstance());
  }
  // Call it once to initialize camera effects.
  OnCameraEffectChanged(GetCameraEffectsController()->GetCameraEffects());
}

void VcBackgroundUISeaPenProviderImpl::SelectRecentSeaPenImageInternal(
    const uint32_t id,
    const bool preview_mode,
    SelectRecentSeaPenImageCallback callback) {
  if (preview_mode) {
    sea_pen_receiver_.ReportBadMessage(
        "Preview mode is only used for wallpaper");
    return;
  }
  GetCameraEffectsController()->SetBackgroundImage(
      CameraEffectsController::SeaPenIdToRelativePath(id), std::move(callback));
}

bool VcBackgroundUISeaPenProviderImpl::IsManagedSeaPenEnabledInternal() {
  return ::ash::personalization_app::IsManagedSeaPenVcBackgroundEnabled(
      profile_);
}

bool VcBackgroundUISeaPenProviderImpl::
    IsManagedSeaPenFeedbackEnabledInternal() {
  return ::ash::personalization_app::IsManagedSeaPenVcBackgroundFeedbackEnabled(
      profile_);
}

void VcBackgroundUISeaPenProviderImpl::GetRecentSeaPenImageIdsInternal(
    GetRecentSeaPenImageIdsCallback callback) {
  GetCameraEffectsController()->GetBackgroundImageFileNames(
      base::BindOnce(&GetIdsFromFilePaths).Then(std::move(callback)));
}

void VcBackgroundUISeaPenProviderImpl::GetRecentSeaPenImageThumbnailInternal(
    const uint32_t id,
    SeaPenWallpaperManager::GetImageAndMetadataCallback callback) {
  GetCameraEffectsController()->GetBackgroundImageInfo(
      CameraEffectsController::SeaPenIdToRelativePath(id),
      base::BindOnce(&OnGetBackgroundImageInfo, std::move(callback)));
}

void VcBackgroundUISeaPenProviderImpl::
    ShouldShowSeaPenIntroductionDialogInternal(
        ShouldShowSeaPenIntroductionDialogCallback callback) {
  std::move(callback).Run(contextual_tooltip::ShouldShowNudge(
      profile_->GetPrefs(),
      contextual_tooltip::TooltipType::kSeaPenVcBackgroundIntroDialog,
      /*recheck_delay=*/nullptr));
}

void VcBackgroundUISeaPenProviderImpl::
    HandleSeaPenIntroductionDialogClosedInternal() {
  contextual_tooltip::HandleGesturePerformed(
      profile_->GetPrefs(),
      contextual_tooltip::TooltipType::kSeaPenVcBackgroundIntroDialog);
}

void VcBackgroundUISeaPenProviderImpl::DeleteRecentSeaPenImage(
    const uint32_t id,
    DeleteRecentSeaPenImageCallback callback) {
  if (recent_sea_pen_image_ids_.count(id) == 0) {
    sea_pen_receiver_.ReportBadMessage("Invalid Sea Pen image received");
    return;
  }

  GetCameraEffectsController()->RemoveBackgroundImage(
      CameraEffectsController::SeaPenIdToRelativePath(id), std::move(callback));
}

void VcBackgroundUISeaPenProviderImpl::OnCameraEffectChanged(
    const cros::mojom::EffectsConfigPtr& new_effects) {
  user_manager::User* active_user =
      user_manager::UserManager::Get()->GetActiveUser();
  DCHECK(active_user);

  if (active_user->GetAccountId() !=
      personalization_app::GetAccountId(profile_)) {
    DVLOG(1) << __func__ << " ignore camera effect update for other user";
    return;
  }

  if (new_effects.is_null() || !new_effects->replace_enabled ||
      !new_effects->background_filepath) {
    sea_pen_observer_remote_->OnSelectedSeaPenImageChanged(std::nullopt);
    return;
  }

  sea_pen_observer_remote_->OnSelectedSeaPenImageChanged(
      GetIdFromFileName(new_effects->background_filepath.value()));
}

void VcBackgroundUISeaPenProviderImpl::OnFetchWallpaperDoneInternal(
    const SeaPenImage& sea_pen_image,
    const ash::personalization_app::mojom::SeaPenQueryPtr& query,
    const bool preview_mode,
    base::OnceCallback<void(bool success)> callback) {
  if (preview_mode) {
    sea_pen_receiver_.ReportBadMessage(
        "Preview mode is only used for wallpaper");
    return;
  }
  const std::optional<std::string> metadata =
      base::WriteJson(SeaPenQueryToDict(query));
  if (!metadata.has_value()) {
    LOG(WARNING) << "Failed to write json metadata";
  }
  GetCameraEffectsController()->SetBackgroundImageFromContent(
      sea_pen_image, metadata.value_or(std::string()), std::move(callback));
}

}  // namespace ash::vc_background_ui