chromium/ash/public/cpp/wallpaper/wallpaper_info.cc

// Copyright 2021 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/public/cpp/wallpaper/wallpaper_info.h"

#include <iostream>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/wallpaper/online_wallpaper_params.h"
#include "ash/public/cpp/wallpaper/wallpaper_types.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/types/cxx23_to_underlying.h"
#include "base/version.h"

namespace ash {

namespace {

// Populates online wallpaper related info in `info`.
void PopulateOnlineWallpaperInfo(WallpaperInfo* info,
                                 const base::Value::Dict& info_dict) {
  const std::string* asset_id_str =
      info_dict.FindString(WallpaperInfo::kNewWallpaperAssetIdNodeName);
  const std::string* collection_id =
      info_dict.FindString(WallpaperInfo::kNewWallpaperCollectionIdNodeName);
  const std::string* dedup_key =
      info_dict.FindString(WallpaperInfo::kNewWallpaperDedupKeyNodeName);
  const std::string* unit_id_str =
      info_dict.FindString(WallpaperInfo::kNewWallpaperUnitIdNodeName);
  const base::Value::List* variant_list =
      info_dict.FindList(WallpaperInfo::kNewWallpaperVariantListNodeName);

  info->collection_id = collection_id ? *collection_id : std::string();
  info->dedup_key = dedup_key ? std::make_optional(*dedup_key) : std::nullopt;

  if (asset_id_str) {
    uint64_t asset_id;
    if (base::StringToUint64(*asset_id_str, &asset_id)) {
      info->asset_id = std::make_optional(asset_id);
    }
  }
  if (unit_id_str) {
    uint64_t unit_id;
    if (base::StringToUint64(*unit_id_str, &unit_id)) {
      info->unit_id = std::make_optional(unit_id);
    }
  }
  if (variant_list) {
    std::vector<OnlineWallpaperVariant> variants;
    for (const auto& variant_info_value : *variant_list) {
      if (!variant_info_value.is_dict()) {
        continue;
      }
      const base::Value::Dict& variant_info = variant_info_value.GetDict();
      const std::string* variant_asset_id_str =
          variant_info.FindString(WallpaperInfo::kNewWallpaperAssetIdNodeName);
      const std::string* url =
          variant_info.FindString(WallpaperInfo::kOnlineWallpaperUrlNodeName);
      std::optional<int> type =
          variant_info.FindInt(WallpaperInfo::kOnlineWallpaperTypeNodeName);
      if (variant_asset_id_str && url && type.has_value()) {
        uint64_t variant_asset_id;
        if (base::StringToUint64(*variant_asset_id_str, &variant_asset_id)) {
          variants.emplace_back(
              variant_asset_id, GURL(*url),
              static_cast<backdrop::Image::ImageType>(type.value()));
        }
      }
    }
    info->variants = std::move(variants);
  }
}

}  // namespace

WallpaperInfo::WallpaperInfo() {
  layout = WALLPAPER_LAYOUT_CENTER;
  type = WallpaperType::kCount;
}

WallpaperInfo::WallpaperInfo(
    const OnlineWallpaperParams& online_wallpaper_params,
    const OnlineWallpaperVariant& target_variant)
    : location(target_variant.raw_url.spec()),
      layout(online_wallpaper_params.layout),
      type(online_wallpaper_params.daily_refresh_enabled
               ? WallpaperType::kDaily
               : WallpaperType::kOnline),
      date(base::Time::Now()),
      collection_id(online_wallpaper_params.collection_id),
      unit_id(online_wallpaper_params.unit_id),
      variants(online_wallpaper_params.variants) {
  if (features::IsVersionWallpaperInfoEnabled()) {
    version = GetSupportedVersion(type);
  } else {
    asset_id = target_variant.asset_id;
  }
}

WallpaperInfo::WallpaperInfo(
    const GooglePhotosWallpaperParams& google_photos_wallpaper_params)
    : layout(google_photos_wallpaper_params.layout), date(base::Time::Now()) {
  if (google_photos_wallpaper_params.daily_refresh_enabled) {
    type = WallpaperType::kDailyGooglePhotos;
    collection_id = google_photos_wallpaper_params.id;
  } else {
    type = WallpaperType::kOnceGooglePhotos;
    location = google_photos_wallpaper_params.id;
    dedup_key = google_photos_wallpaper_params.dedup_key;
  }
  if (features::IsVersionWallpaperInfoEnabled()) {
    version = GetSupportedVersion(type);
  }
}

WallpaperInfo::WallpaperInfo(const std::string& in_location,
                             WallpaperLayout in_layout,
                             WallpaperType in_type,
                             const base::Time& in_date,
                             const std::string& in_user_file_path)
    : location(in_location),
      user_file_path(in_user_file_path),
      layout(in_layout),
      type(in_type),
      date(in_date) {
  if (features::IsVersionWallpaperInfoEnabled()) {
    version = GetSupportedVersion(type);
  }
}

WallpaperInfo::WallpaperInfo(const WallpaperInfo& other) = default;
WallpaperInfo& WallpaperInfo::operator=(const WallpaperInfo& other) = default;

WallpaperInfo::WallpaperInfo(WallpaperInfo&& other) = default;
WallpaperInfo& WallpaperInfo::operator=(WallpaperInfo&& other) = default;

bool WallpaperInfo::MatchesSelection(const WallpaperInfo& other) const {
  if (features::IsVersionWallpaperInfoEnabled()) {
    // Checks for exact match of the version here to avoid unexpected data
    // mismatch between two WallpaperInfos. Any difference in version should
    // surface to the callers of this function so they can decide how to handle
    // it.
    if (!version.IsValid() || !other.version.IsValid() ||
        version != other.version) {
      return false;
    }
  }
  // |location| are skipped on purpose in favor of |unit_id| as
  // online wallpapers can vary across devices due to their color mode. Other
  // wallpaper types still require location to be equal.
  switch (type) {
    case WallpaperType::kOnline:
    case WallpaperType::kDaily:
      return type == other.type && layout == other.layout &&
             collection_id == other.collection_id && unit_id == other.unit_id &&
             base::ranges::equal(variants, other.variants);
    case WallpaperType::kOnceGooglePhotos:
    case WallpaperType::kDailyGooglePhotos:
      return location == other.location && layout == other.layout &&
             collection_id == other.collection_id;
    case WallpaperType::kCustomized:
      // |location| is skipped for customized wallpaper as it includes files id
      // which is different between devices even it refers to the same file.
      // Comparing |user_file_path| that contains the absolute path should be
      // enough.
      return type == other.type && layout == other.layout &&
             user_file_path == other.user_file_path;
    case WallpaperType::kSeaPen:
    case WallpaperType::kDefault:
    case WallpaperType::kPolicy:
    case WallpaperType::kThirdParty:
    case WallpaperType::kDevice:
    case WallpaperType::kOneShot:
    case WallpaperType::kOobe:
    case WallpaperType::kCount:
      return type == other.type && layout == other.layout &&
             location == other.location;
  }
}

bool WallpaperInfo::MatchesAsset(const WallpaperInfo& other) const {
  if (!MatchesSelection(other))
    return false;

  switch (type) {
    case WallpaperType::kOnline:
    case WallpaperType::kDaily:
      return location == other.location;
    case WallpaperType::kOnceGooglePhotos:
    case WallpaperType::kDailyGooglePhotos:
    case WallpaperType::kCustomized:
    case WallpaperType::kDefault:
    case WallpaperType::kPolicy:
    case WallpaperType::kThirdParty:
    case WallpaperType::kDevice:
    case WallpaperType::kOneShot:
    case WallpaperType::kOobe:
    case WallpaperType::kSeaPen:
    case WallpaperType::kCount:
      return true;
  }
}

// static
std::optional<WallpaperInfo> WallpaperInfo::FromDict(
    const base::Value::Dict& dict) {
  const std::string* location =
      dict.FindString(WallpaperInfo::kNewWallpaperLocationNodeName);
  const std::string* file_path =
      dict.FindString(WallpaperInfo::kNewWallpaperUserFilePathNodeName);
  std::optional<int> layout =
      dict.FindInt(WallpaperInfo::kNewWallpaperLayoutNodeName);
  std::optional<int> type =
      dict.FindInt(WallpaperInfo::kNewWallpaperTypeNodeName);
  const std::string* date_string =
      dict.FindString(WallpaperInfo::kNewWallpaperDateNodeName);

  if (!location || !layout || !type || !date_string) {
    return std::nullopt;
  }

  // Perform special handling of pref values >= kCount before hitting the DCHECK
  // below. This can happen in normal operation when syncing from a newer
  // release to an older one, so should not DCHECK.
  if (type.value() >= base::to_underlying(WallpaperType::kCount)) {
    LOG(WARNING) << "Skipping wallpaper sync due to unrecognized WallpaperType="
                 << type.value()
                 << ". This likely happened due to sync from a newer version "
                    "of ChromeOS.";
    return std::nullopt;
  }

  WallpaperType wallpaper_type = static_cast<WallpaperType>(type.value());
  DCHECK(IsAllowedInPrefs(wallpaper_type))
      << "Invalid WallpaperType=" << base::to_underlying(wallpaper_type)
      << " in prefs";

  WallpaperInfo info;
  info.type = wallpaper_type;

  const std::string* version =
      dict.FindString(WallpaperInfo::kNewWallpaperVersionNodeName);
  if (version) {
    info.version = base::Version(*version);
  }

  int64_t date_val;
  if (!base::StringToInt64(*date_string, &date_val)) {
    return std::nullopt;
  }

  info.location = *location;
  // The old wallpaper didn't include file path information. For migration,
  // check whether file_path is a null pointer before setting user_file_path.
  info.user_file_path = file_path ? *file_path : "";
  info.layout = static_cast<WallpaperLayout>(layout.value());
  if (info.layout >= WallpaperLayout::NUM_WALLPAPER_LAYOUT) {
    LOG(WARNING) << "Invalid WallpaperLayout=" << info.layout << " in prefs";
    return std::nullopt;
  }
  // TODO(skau): Switch to TimeFromValue
  info.date =
      base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(date_val));
  PopulateOnlineWallpaperInfo(&info, dict);
  return info;
}

base::Value::Dict WallpaperInfo::ToDict() const {
  base::Value::Dict wallpaper_info_dict;
  if (version.IsValid()) {
    wallpaper_info_dict.Set(kNewWallpaperVersionNodeName, version.GetString());
  }
  if (asset_id.has_value()) {
    wallpaper_info_dict.Set(kNewWallpaperAssetIdNodeName,
                            base::NumberToString(asset_id.value()));
  }
  if (unit_id.has_value()) {
    wallpaper_info_dict.Set(kNewWallpaperUnitIdNodeName,
                            base::NumberToString(unit_id.value()));
  }
  base::Value::List online_wallpaper_variant_list;
  for (const auto& variant : variants) {
    base::Value::Dict online_wallpaper_variant_dict;
    online_wallpaper_variant_dict.Set(kNewWallpaperAssetIdNodeName,
                                      base::NumberToString(variant.asset_id));
    online_wallpaper_variant_dict.Set(kOnlineWallpaperUrlNodeName,
                                      variant.raw_url.spec());
    online_wallpaper_variant_dict.Set(kOnlineWallpaperTypeNodeName,
                                      static_cast<int>(variant.type));
    online_wallpaper_variant_list.Append(
        std::move(online_wallpaper_variant_dict));
  }

  wallpaper_info_dict.Set(kNewWallpaperVariantListNodeName,
                          std::move(online_wallpaper_variant_list));
  wallpaper_info_dict.Set(kNewWallpaperCollectionIdNodeName, collection_id);
  // TODO(skau): Change time representation to TimeToValue.
  wallpaper_info_dict.Set(
      kNewWallpaperDateNodeName,
      base::NumberToString(date.ToDeltaSinceWindowsEpoch().InMicroseconds()));
  if (dedup_key) {
    wallpaper_info_dict.Set(kNewWallpaperDedupKeyNodeName, dedup_key.value());
  }
  wallpaper_info_dict.Set(kNewWallpaperLocationNodeName, location);
  wallpaper_info_dict.Set(kNewWallpaperUserFilePathNodeName, user_file_path);
  wallpaper_info_dict.Set(kNewWallpaperLayoutNodeName, layout);
  wallpaper_info_dict.Set(kNewWallpaperTypeNodeName, static_cast<int>(type));
  return wallpaper_info_dict;
}

WallpaperInfo::~WallpaperInfo() = default;

std::ostream& operator<<(std::ostream& os, const WallpaperInfo& info) {
  os << "WallpaperInfo:" << std::endl;
  os << "  location: " << info.location << std::endl;
  os << "  user_file_path: " << info.user_file_path << std::endl;
  os << "  layout: " << info.layout << std::endl;
  os << "  type: " << static_cast<int>(info.type) << std::endl;
  os << "  date: " << info.date << std::endl;
  os << "  dedup_key: " << info.dedup_key.value_or("") << std::endl;
  os << "  asset_id: " << info.asset_id.value_or(-1) << std::endl;
  os << "  collection_id: " << info.collection_id << std::endl;
  os << "  unit_id: " << info.unit_id.value_or(-1) << std::endl;
  os << "  variants_size: " << info.variants.size() << std::endl;
  os << "  version: " << info.version.GetString() << std::endl;
  return os;
}

}  // namespace ash