chromium/ash/wallpaper/online_wallpaper_variant_info_fetcher.cc

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

#include "ash/wallpaper/online_wallpaper_variant_info_fetcher.h"

#include "ash/public/cpp/schedule_enums.h"
#include "ash/public/cpp/wallpaper/online_wallpaper_params.h"
#include "ash/public/cpp/wallpaper/online_wallpaper_variant.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller_client.h"
#include "ash/public/cpp/wallpaper/wallpaper_info.h"
#include "ash/wallpaper/wallpaper_constants.h"
#include "ash/wallpaper/wallpaper_metrics_manager.h"
#include "ash/wallpaper/wallpaper_utils/wallpaper_online_variant_utils.h"
#include "ash/webui/personalization_app/proto/backdrop_wallpaper.pb.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/task/sequenced_task_runner.h"

namespace ash {
namespace {

OnlineWallpaperVariantInfoFetcher* g_instance = nullptr;

// The filtered results from a set of backdrop::Images for a given |location|
// and |unit_id| value.
class VariantMatches {
 public:
  VariantMatches(VariantMatches&&) = default;

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

  ~VariantMatches() = default;

  // Filters |images| to only the entries that match |location|.
  static std::optional<VariantMatches> FromImages(
      const std::string& location,
      const std::vector<backdrop::Image>& images) {
    // Find the exact image in the |images| collection.
    auto image_iter =
        base::ranges::find(images, location, &backdrop::Image::image_url);

    if (image_iter == images.end()) {
      return std::nullopt;
    }

    uint64_t unit_id = image_iter->unit_id();
    return FromImages(unit_id, images);
  }

  // Same semantic as the method above but instead of matching against
  // `location`, `unit_id` is used instead.
  static std::optional<VariantMatches> FromImages(
      uint64_t unit_id,
      const std::vector<backdrop::Image>& images) {
    std::vector<OnlineWallpaperVariant> variants;
    for (const auto& image : images) {
      if (image.unit_id() == unit_id) {
        variants.emplace_back(image.asset_id(), GURL(image.image_url()),
                              image.has_image_type()
                                  ? image.image_type()
                                  : backdrop::Image::IMAGE_TYPE_UNKNOWN);
      }
    }
    if (variants.empty()) {
      return std::nullopt;
    }
    return VariantMatches(unit_id, std::move(variants));
  }

  // The unit id of the Variant that matched |location|.
  const uint64_t unit_id;

  // The set of images that are appropriate for |location|.
  const std::vector<OnlineWallpaperVariant> variants;

 private:
  VariantMatches(uint64_t unit_id_in,
                 std::vector<OnlineWallpaperVariant>&& variants_in)
      : unit_id(unit_id_in), variants(variants_in) {}
};

bool IsDaily(const WallpaperInfo& info) {
  return info.type == WallpaperType::kDaily;
}

}  // namespace

OnlineWallpaperVariantInfoFetcher::OnlineWallpaperRequest::
    OnlineWallpaperRequest(const AccountId& account_id_in,
                           const std::string& collection_id_in,
                           WallpaperLayout layout_in,
                           bool daily_refresh_enabled_in)
    : account_id(account_id_in),
      collection_id(collection_id_in),
      layout(layout_in),
      daily_refresh_enabled(daily_refresh_enabled_in) {}

OnlineWallpaperVariantInfoFetcher::OnlineWallpaperRequest::
    ~OnlineWallpaperRequest() = default;

OnlineWallpaperVariantInfoFetcher::OnlineWallpaperVariantInfoFetcher() {
  DCHECK_EQ(nullptr, g_instance);
  g_instance = this;
}

OnlineWallpaperVariantInfoFetcher::~OnlineWallpaperVariantInfoFetcher() {
  DCHECK_EQ(g_instance, this);
  g_instance = nullptr;
}

// static
OnlineWallpaperVariantInfoFetcher*
OnlineWallpaperVariantInfoFetcher::GetInstance() {
  DCHECK(g_instance);
  return g_instance;
}

void OnlineWallpaperVariantInfoFetcher::SetClient(
    WallpaperControllerClient* client) {
  wallpaper_controller_client_ = client;
}

void OnlineWallpaperVariantInfoFetcher::FetchOnlineWallpaper(
    const AccountId& account_id,
    const WallpaperInfo& info,
    FetchParamsCallback callback) {
  DCHECK(IsOnlineWallpaper(info.type));

  DCHECK(wallpaper_controller_client_);

  if (info.unit_id.has_value() && !info.variants.empty()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback),
                                  OnlineWallpaperParams{
                                      account_id, info.collection_id,
                                      info.layout, /*preview_mode=*/false,
                                      /*from_user=*/false, IsDaily(info),
                                      info.unit_id.value(), info.variants}));
    return;
  }

  // For requests from existing WallpaperInfo, location should always be
  // populated. In the event of very old wallpapers, treat them as failure.
  if (info.location.empty()) {
    LOG(WARNING)
        << "Failed to determine wallpaper url. This should only happen for "
           "very old wallpapers.";
    base::UmaHistogramEnumeration(
        WallpaperMetricsManager::ToResultHistogram(WallpaperType::kOnline),
        SetWallpaperResult::kInvalidState);
    std::move(callback).Run(std::nullopt);
    return;
  }

  bool daily = IsDaily(info);
  auto request = std::make_unique<OnlineWallpaperRequest>(
      account_id, info.collection_id, info.layout, daily);

  auto collection_id = request->collection_id;
  wallpaper_controller_client_->FetchImagesForCollection(
      collection_id,
      base::BindOnce(
          &OnlineWallpaperVariantInfoFetcher::FindAndSetOnlineWallpaperVariants,
          weak_factory_.GetWeakPtr(), std::move(request), info.location,
          std::move(callback)));
}

bool OnlineWallpaperVariantInfoFetcher::FetchDailyWallpaper(
    const AccountId& account_id,
    const WallpaperInfo& info,
    FetchParamsCallback callback) {
  DCHECK(IsDaily(info));

  // We might not have a client yet.
  if (!wallpaper_controller_client_) {
    return false;
  }

  bool daily = true;  // This is always a a daily wallpaper.
  auto request = std::make_unique<OnlineWallpaperRequest>(
      account_id, info.collection_id, info.layout, daily);
  wallpaper_controller_client_->FetchDailyRefreshWallpaper(
      info.collection_id,
      base::BindOnce(&OnlineWallpaperVariantInfoFetcher::OnSingleFetch,
                     weak_factory_.GetWeakPtr(), std::move(request),
                     std::move(callback)));
  return true;
}

void OnlineWallpaperVariantInfoFetcher::FetchTimeOfDayWallpaper(
    const AccountId& account_id,
    uint64_t unit_id,
    FetchParamsCallback callback) {
  auto request = std::make_unique<OnlineWallpaperRequest>(
      account_id, wallpaper_constants::kTimeOfDayWallpaperCollectionId,
      WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
      /*daily_refresh_enabled=*/false);

  wallpaper_controller_client_->FetchImagesForCollection(
      wallpaper_constants::kTimeOfDayWallpaperCollectionId,
      base::BindOnce(
          &OnlineWallpaperVariantInfoFetcher::OnTimeOfDayWallpapersFetched,
          weak_factory_.GetWeakPtr(), std::move(request), unit_id,
          std::move(callback)));
}

void OnlineWallpaperVariantInfoFetcher::OnSingleFetch(
    std::unique_ptr<OnlineWallpaperRequest> request,
    FetchParamsCallback callback,
    bool success,
    const backdrop::Image& image) {
  if (!success) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  // If wallpaper_controller_client_ is null, we're shutting down.
  if (!wallpaper_controller_client_)
    return;

  auto collection_id = request->collection_id;
  wallpaper_controller_client_->FetchImagesForCollection(
      collection_id,
      base::BindOnce(
          &OnlineWallpaperVariantInfoFetcher::FindAndSetOnlineWallpaperVariants,
          weak_factory_.GetWeakPtr(), std::move(request), image.image_url(),
          std::move(callback)));
}

void OnlineWallpaperVariantInfoFetcher::FindAndSetOnlineWallpaperVariants(
    std::unique_ptr<OnlineWallpaperRequest> request,
    const std::string& location,
    FetchParamsCallback callback,
    bool success,
    const std::vector<backdrop::Image>& images) {
  if (!success) {
    LOG(WARNING) << "Failed to fetch online wallpapers";
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::optional<VariantMatches> matches =
      VariantMatches::FromImages(location, images);
  if (!matches) {
    LOG(ERROR) << "No valid variants";
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::move(callback).Run(ash::OnlineWallpaperParams{
      request->account_id, request->collection_id, request->layout,
      /*preview_mode=*/false, /*from_user=*/false,
      request->daily_refresh_enabled, matches->unit_id, matches->variants});
}

void OnlineWallpaperVariantInfoFetcher::OnTimeOfDayWallpapersFetched(
    std::unique_ptr<OnlineWallpaperRequest> request,
    uint64_t unit_id,
    FetchParamsCallback callback,
    bool success,
    const std::vector<backdrop::Image>& images) {
  if (!success) {
    LOG(WARNING) << "Failed to fetch online wallpapers";
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::optional<VariantMatches> matches =
      VariantMatches::FromImages(unit_id, images);
  if (!matches) {
    LOG(ERROR) << "No valid variants";
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::move(callback).Run(ash::OnlineWallpaperParams{
      request->account_id, request->collection_id, request->layout,
      /*preview_mode=*/false, /*from_user=*/false,
      request->daily_refresh_enabled, matches->unit_id, matches->variants});
}

}  // namespace ash