// 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 "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.h"
#include <stdint.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/controls/contextual_tooltip.h"
#include "ash/public/cpp/image_util.h"
#include "ash/public/cpp/wallpaper/google_photos_wallpaper_params.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.h"
#include "ash/public/cpp/wallpaper/wallpaper_info.h"
#include "ash/public/cpp/wallpaper/wallpaper_types.h"
#include "ash/wallpaper/sea_pen_wallpaper_manager.h"
#include "ash/wallpaper/wallpaper_constants.h"
#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
#include "ash/wallpaper/wallpaper_utils/wallpaper_online_variant_utils.h"
#include "ash/wallpaper/wallpaper_utils/wallpaper_resizer.h"
#include "ash/webui/common/mojom/sea_pen.mojom.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
#include "ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h"
#include "ash/webui/personalization_app/proto/backdrop_wallpaper.pb.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/unguessable_token.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_manager.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_manager_factory.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.h"
#include "chrome/browser/ash/wallpaper/wallpaper_enumerator.h"
#include "chrome/browser/ash/wallpaper_handlers/wallpaper_fetcher_delegate.h"
#include "chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/thumbnail_loader/thumbnail_loader.h"
#include "chrome/browser/ui/ash/wallpaper/wallpaper_controller_client_impl.h"
#include "chrome/browser/ui/webui/sanitized_image_source.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/display/screen.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_util.h"
#include "url/gurl.h"
#include "url/mojom/url.mojom-forward.h"
namespace ash::personalization_app {
namespace {
using ash::WallpaperController;
using ash::personalization_app::GetAccountId;
using ash::personalization_app::GetUser;
constexpr int kLocalImageThumbnailSizeDip = 384;
// Return the online wallpaper key. Use |info.unit_id| if available so we might
// be able to fallback to the cached attribution.
const std::string GetOnlineWallpaperKey(ash::WallpaperInfo info) {
return info.unit_id.has_value() ? base::NumberToString(info.unit_id.value())
: base::UnguessableToken::Create().ToString();
}
GURL GetBitmapJpegDataUrl(const SkBitmap& bitmap) {
std::vector<unsigned char> output;
if (!gfx::JPEGCodec::Encode(bitmap, /*quality=*/100, &output)) {
LOG(ERROR) << "Unable to encode bitmap";
return GURL();
}
GURL data_url =
GetJpegDataUrl({reinterpret_cast<char*>(output.data()), output.size()});
// @see `url.mojom` warning about dropping urls that are too long.
DCHECK_LT(data_url.spec().size(), url::mojom::kMaxURLChars);
return data_url;
}
} // namespace
PersonalizationAppWallpaperProviderImpl::
PersonalizationAppWallpaperProviderImpl(
content::WebUI* web_ui,
std::unique_ptr<wallpaper_handlers::WallpaperFetcherDelegate>
wallpaper_fetcher_delegate)
: web_ui_(web_ui),
profile_(Profile::FromWebUI(web_ui)),
wallpaper_fetcher_delegate_(std::move(wallpaper_fetcher_delegate)) {
content::URLDataSource::Add(profile_,
std::make_unique<SanitizedImageSource>(profile_));
}
PersonalizationAppWallpaperProviderImpl::
~PersonalizationAppWallpaperProviderImpl() {
if (!image_unit_id_map_.empty()) {
// User viewed wallpaper page at least once during this session because
// |image_unit_id_map_| has wallpaper unit ids saved. Check if this user
// should see a wallpaper HaTS.
::ash::personalization_app::PersonalizationAppManagerFactory::
GetForBrowserContext(profile_)
->MaybeStartHatsTimer(
::ash::personalization_app::HatsSurveyType::kWallpaper);
}
CancelPreviewWallpaper();
}
void PersonalizationAppWallpaperProviderImpl::BindInterface(
mojo::PendingReceiver<ash::personalization_app::mojom::WallpaperProvider>
receiver) {
wallpaper_receiver_.reset();
wallpaper_receiver_.Bind(std::move(receiver));
}
void PersonalizationAppWallpaperProviderImpl::GetWallpaperAsJpegBytes(
content::WebUIDataSource::GotDataCallback callback) {
WallpaperController::Get()->LoadPreviewImage(std::move(callback));
}
bool PersonalizationAppWallpaperProviderImpl::IsEligibleForGooglePhotos() {
return GetUser(profile_)->HasGaiaAccount();
}
void PersonalizationAppWallpaperProviderImpl::MakeTransparent() {
WallpaperControllerClientImpl::Get()->MakeTransparent(
web_ui_->GetWebContents());
}
void PersonalizationAppWallpaperProviderImpl::MakeOpaque() {
WallpaperControllerClientImpl::Get()->MakeOpaque(web_ui_->GetWebContents());
}
void PersonalizationAppWallpaperProviderImpl::FetchCollections(
FetchCollectionsCallback callback) {
pending_collections_callbacks_.push_back(std::move(callback));
if (wallpaper_collection_info_fetcher_) {
// Collection fetching already started. No need to start a second time.
return;
}
wallpaper_collection_info_fetcher_ =
wallpaper_fetcher_delegate_->CreateBackdropCollectionInfoFetcher();
// base::Unretained is safe to use because |this| outlives
// |wallpaper_collection_info_fetcher_|.
wallpaper_collection_info_fetcher_->Start(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnFetchCollections,
base::Unretained(this)));
}
void PersonalizationAppWallpaperProviderImpl::FetchImagesForCollection(
const std::string& collection_id,
FetchImagesForCollectionCallback callback) {
auto wallpaper_images_info_fetcher =
wallpaper_fetcher_delegate_->CreateBackdropImageInfoFetcher(
collection_id);
auto* wallpaper_images_info_fetcher_ptr = wallpaper_images_info_fetcher.get();
wallpaper_images_info_fetcher_ptr->Start(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnFetchCollectionImages,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(wallpaper_images_info_fetcher)));
}
void PersonalizationAppWallpaperProviderImpl::FetchGooglePhotosAlbums(
const std::optional<std::string>& resume_token,
FetchGooglePhotosAlbumsCallback callback) {
if (!is_google_photos_enterprise_enabled_) {
wallpaper_receiver_.ReportBadMessage(
"Cannot call `FetchGooglePhotosAlbums()` without confirming that the "
"Google Photos enterprise setting is enabled.");
return;
}
GetOrCreateGooglePhotosAlbumsFetcher()->AddRequestAndStartIfNecessary(
resume_token, std::move(callback));
}
void PersonalizationAppWallpaperProviderImpl::FetchGooglePhotosSharedAlbums(
const std::optional<std::string>& resume_token,
FetchGooglePhotosAlbumsCallback callback) {
if (!is_google_photos_enterprise_enabled_) {
wallpaper_receiver_.ReportBadMessage(
"Cannot call `FetchGooglePhotosAlbums()` without confirming that the "
"Google Photos enterprise setting is enabled.");
return;
}
GetOrCreateGooglePhotosSharedAlbumsFetcher()->AddRequestAndStartIfNecessary(
resume_token, std::move(callback));
}
void PersonalizationAppWallpaperProviderImpl::FetchGooglePhotosEnabled(
FetchGooglePhotosEnabledCallback callback) {
if (!IsEligibleForGooglePhotos()) {
wallpaper_receiver_.ReportBadMessage(
"Cannot call `FetchGooglePhotosEnabled()` without Google Photos "
"Wallpaper integration enabled.");
std::move(callback).Run(
ash::personalization_app::mojom::GooglePhotosEnablementState::kError);
return;
}
// base::Unretained is safe to use because |this| outlives
// |google_photos_enabled_fetcher_|.
GetOrCreateGooglePhotosEnabledFetcher()->AddRequestAndStartIfNecessary(
base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnFetchGooglePhotosEnabled,
base::Unretained(this), std::move(callback)));
}
void PersonalizationAppWallpaperProviderImpl::FetchGooglePhotosPhotos(
const std::optional<std::string>& item_id,
const std::optional<std::string>& album_id,
const std::optional<std::string>& resume_token,
FetchGooglePhotosPhotosCallback callback) {
if (!is_google_photos_enterprise_enabled_) {
wallpaper_receiver_.ReportBadMessage(
"Cannot call `FetchGooglePhotosPhotos()` without confirming that the "
"Google Photos enterprise setting is enabled.");
std::move(callback).Run(
ash::personalization_app::mojom::FetchGooglePhotosPhotosResponse::New(
std::nullopt, std::nullopt));
return;
}
GetOrCreateGooglePhotosPhotosFetcher()->AddRequestAndStartIfNecessary(
item_id, album_id, resume_token, /*shuffle=*/false,
base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnFetchGooglePhotosPhotos,
weak_ptr_factory_.GetWeakPtr(), album_id, std::move(callback)));
}
void PersonalizationAppWallpaperProviderImpl::GetDefaultImageThumbnail(
GetDefaultImageThumbnailCallback callback) {
auto* wallpaper_controller = WallpaperController::Get();
const user_manager::User* user = GetUser(profile_);
base::FilePath default_wallpaper_path =
wallpaper_controller->GetDefaultWallpaperPath(user->GetType());
if (default_wallpaper_path.empty()) {
std::move(callback).Run(GURL());
return;
}
image_util::DecodeImageFile(
base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnGetDefaultImage,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
default_wallpaper_path);
}
void PersonalizationAppWallpaperProviderImpl::GetLocalImages(
GetLocalImagesCallback callback) {
// TODO(b/190062481) also load images from android files.
ash::EnumerateLocalWallpaperFiles(
profile_,
base::BindOnce(&PersonalizationAppWallpaperProviderImpl::OnGetLocalImages,
backend_weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void PersonalizationAppWallpaperProviderImpl::GetLocalImageThumbnail(
const base::FilePath& path,
GetLocalImageThumbnailCallback callback) {
if (local_images_.count(path) == 0) {
wallpaper_receiver_.ReportBadMessage("Invalid local image path received");
return;
}
if (!thumbnail_loader_) {
thumbnail_loader_ = std::make_unique<ash::ThumbnailLoader>(profile_);
}
ash::ThumbnailLoader::ThumbnailRequest request(
path,
gfx::Size(kLocalImageThumbnailSizeDip, kLocalImageThumbnailSizeDip));
thumbnail_loader_->Load(
request,
base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnGetLocalImageThumbnail,
base::Unretained(this), std::move(callback)));
}
void PersonalizationAppWallpaperProviderImpl::SetWallpaperObserver(
mojo::PendingRemote<ash::personalization_app::mojom::WallpaperObserver>
observer) {
// May already be bound if user refreshes page.
wallpaper_observer_remote_.reset();
wallpaper_observer_remote_.Bind(std::move(observer));
if (!wallpaper_controller_observer_.IsObserving()) {
wallpaper_controller_observer_.Observe(ash::WallpaperController::Get());
}
// Call it once to send the first wallpaper.
OnWallpaperResized();
}
void PersonalizationAppWallpaperProviderImpl::OnWallpaperResized() {
auto* wallpaper_controller = WallpaperController::Get();
DCHECK(wallpaper_controller);
const AccountId account_id = GetAccountId(profile_);
if (wallpaper_controller->CurrentAccountId() != account_id) {
DVLOG(1) << "Skip " << __func__ << " for different AccountId";
return;
}
wallpaper_attribution_info_fetcher_.reset();
attribution_weak_ptr_factory_.InvalidateWeakPtrs();
std::optional<ash::WallpaperInfo> info =
wallpaper_controller->GetWallpaperInfoForAccountId(account_id);
if (!info) {
DVLOG(1) << "No wallpaper info for active user. This should only happen in "
"tests.";
NotifyWallpaperChanged(nullptr);
NotifyAttributionChanged(nullptr);
return;
}
switch (info->type) {
case ash::WallpaperType::kDaily:
case ash::WallpaperType::kOnline: {
if (info->collection_id.empty() || !info->unit_id.has_value()) {
DVLOG(2) << "no collection_id or unit_id found";
// Older versions of ChromeOS do not store these information, need to
// look up all collections and match URL.
FetchCollections(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::FindAttribution,
attribution_weak_ptr_factory_.GetWeakPtr(), *info));
return;
}
backdrop::Collection collection;
collection.set_collection_id(info->collection_id);
FindAttribution(*info, std::vector<backdrop::Collection>{collection});
return;
}
case ash::WallpaperType::kCustomized: {
base::FilePath file_name = base::FilePath(info->location).BaseName();
// Match selected wallpaper based on full filename including extension.
const std::string& key = info->user_file_path.empty()
? file_name.value()
: info->user_file_path;
NotifyWallpaperChanged(
ash::personalization_app::mojom::CurrentWallpaper::New(
info->layout, info->type, key,
/*description_title=*/std::string(),
/*description_content=*/std::string()));
// Do not show file extension in user-visible selected details text.
std::vector<std::string> attribution = {
file_name.RemoveExtension().value()};
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
std::move(attribution), key));
return;
}
case ash::WallpaperType::kDailyGooglePhotos:
case ash::WallpaperType::kOnceGooglePhotos:
WallpaperControllerClientImpl::Get()->FetchGooglePhotosPhoto(
GetAccountId(profile_), info->location,
base::BindOnce(&PersonalizationAppWallpaperProviderImpl::
SendGooglePhotosAttribution,
weak_ptr_factory_.GetWeakPtr(), *info));
return;
case ash::WallpaperType::kDefault:
case ash::WallpaperType::kDevice:
case ash::WallpaperType::kOneShot:
case ash::WallpaperType::kOobe:
case ash::WallpaperType::kPolicy:
case ash::WallpaperType::kThirdParty: {
const std::string key = base::UnguessableToken::Create().ToString();
NotifyWallpaperChanged(
ash::personalization_app::mojom::CurrentWallpaper::New(
info->layout, info->type, key,
/*description_title=*/std::string(),
/*description_content=*/std::string()));
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
std::vector<std::string>(), key));
return;
}
case ash::WallpaperType::kSeaPen: {
const base::FilePath path(info->location);
const std::optional<uint32_t> id = GetIdFromFileName(path);
if (!id.has_value()) {
NotifyWallpaperChanged(nullptr);
NotifyAttributionChanged(nullptr);
return;
}
// TODO(b/307757290) set description content.
NotifyWallpaperChanged(
ash::personalization_app::mojom::CurrentWallpaper::New(
info->layout, info->type,
/*key=*/base::NumberToString(id.value()),
/*description_title=*/std::string(),
/*description_content=*/std::string()));
FindSeaPenWallpaperAttribution(id.value());
return;
}
case ash::WallpaperType::kCount:
break;
}
// This can happen when a WallpaperType object from a different version of
// ChromeOS persists through an upgrade or is synced to a different
// version of ChromeOS. Handle the error as gracefully as possible. Pick a
// safe wallpaper type `kOneShot` to send to personalization app.
const std::string key = base::UnguessableToken::Create().ToString();
NotifyWallpaperChanged(ash::personalization_app::mojom::CurrentWallpaper::New(
info->layout, ash::WallpaperType::kOneShot, key,
/*description_title=*/std::string(),
/*description_content=*/std::string()));
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
std::vector<std::string>(), key));
// Continue to record data on how frequently this happens.
SCOPED_CRASH_KEY_STRING32(
"Wallpaper", "WallpaperType",
base::NumberToString(
static_cast<std::underlying_type_t<WallpaperType>>(info->type)));
base::debug::DumpWithoutCrashing();
return;
}
void PersonalizationAppWallpaperProviderImpl::OnWallpaperPreviewEnded() {
DCHECK(wallpaper_observer_remote_.is_bound());
wallpaper_observer_remote_->OnWallpaperPreviewEnded();
// Make sure to fire another |OnWallpaperResized| after preview is over
// so that personalization app ends up with correct wallpaper state.
OnWallpaperResized();
}
void PersonalizationAppWallpaperProviderImpl::SelectWallpaper(
uint64_t unit_id,
bool preview_mode,
SelectWallpaperCallback callback) {
const auto& it = image_unit_id_map_.find(unit_id);
if (it == image_unit_id_map_.end()) {
wallpaper_receiver_.ReportBadMessage("Invalid image unit_id selected");
return;
}
auto* wallpaper_controller = WallpaperController::Get();
DCHECK(wallpaper_controller);
if (!wallpaper_controller->CanSetUserWallpaper(GetAccountId(profile_))) {
wallpaper_receiver_.ReportBadMessage("Invalid request to set wallpaper");
return;
}
std::string collection_id;
std::vector<ash::OnlineWallpaperVariant> variants;
for (const auto& image_info : it->second) {
variants.emplace_back(image_info.asset_id, image_info.image_url,
image_info.type);
collection_id = image_info.collection_id;
}
if (pending_select_wallpaper_callback_) {
std::move(pending_select_wallpaper_callback_).Run(/*success=*/false);
}
pending_select_wallpaper_callback_ = std::move(callback);
SetMinimizedWindowStateForPreview(preview_mode);
WallpaperControllerClientImpl* client = WallpaperControllerClientImpl::Get();
DCHECK(client);
client->RecordWallpaperSourceUMA(ash::WallpaperType::kOnline);
if (IsTimeOfDayWallpaper(collection_id) &&
features::IsTimeOfDayWallpaperEnabled()) {
// Records the display count of the time of day wallpaper dialog when the
// user selects one to determine whether to show it the next time.
contextual_tooltip::HandleGesturePerformed(
profile_->GetPrefs(),
contextual_tooltip::TooltipType::kTimeOfDayWallpaperDialog);
}
wallpaper_controller->SetOnlineWallpaper(
ash::OnlineWallpaperParams(
GetAccountId(profile_), collection_id,
ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED, preview_mode,
/*from_user=*/true,
/*daily_refresh_enabled=*/false, unit_id, variants),
base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnOnlineWallpaperSelected,
backend_weak_ptr_factory_.GetWeakPtr()));
}
void PersonalizationAppWallpaperProviderImpl::SelectDefaultImage(
SelectDefaultImageCallback callback) {
WallpaperControllerClientImpl* client = WallpaperControllerClientImpl::Get();
DCHECK(client);
client->RecordWallpaperSourceUMA(ash::WallpaperType::kDefault);
WallpaperController::Get()->SetDefaultWallpaper(
GetAccountId(profile_), /*show_wallpaper=*/true, std::move(callback));
}
void PersonalizationAppWallpaperProviderImpl::SelectLocalImage(
const base::FilePath& path,
ash::WallpaperLayout layout,
bool preview_mode,
SelectLocalImageCallback callback) {
if (local_images_.count(path) == 0) {
wallpaper_receiver_.ReportBadMessage("Invalid local image path selected");
return;
}
ash::WallpaperController* wallpaper_controller = WallpaperController::Get();
DCHECK(wallpaper_controller);
if (!wallpaper_controller->CanSetUserWallpaper(GetAccountId(profile_))) {
wallpaper_receiver_.ReportBadMessage("Invalid request to set wallpaper");
return;
}
if (pending_select_local_image_callback_) {
std::move(pending_select_local_image_callback_).Run(/*success=*/false);
}
pending_select_local_image_callback_ = std::move(callback);
SetMinimizedWindowStateForPreview(preview_mode);
WallpaperControllerClientImpl::Get()->RecordWallpaperSourceUMA(
ash::WallpaperType::kCustomized);
wallpaper_controller->SetCustomWallpaper(
GetAccountId(profile_), path, layout, preview_mode,
base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnLocalImageSelected,
backend_weak_ptr_factory_.GetWeakPtr()));
}
void PersonalizationAppWallpaperProviderImpl::SelectGooglePhotosPhoto(
const std::string& id,
ash::WallpaperLayout layout,
bool preview_mode,
SelectGooglePhotosPhotoCallback callback) {
if (!is_google_photos_enterprise_enabled_) {
wallpaper_receiver_.ReportBadMessage(
"Cannot call `SelectGooglePhotosPhoto()` without confirming that the "
"Google Photos enterprise setting is enabled.");
std::move(callback).Run(false);
return;
}
auto* wallpaper_controller = WallpaperController::Get();
DCHECK(wallpaper_controller);
if (!wallpaper_controller->CanSetUserWallpaper(GetAccountId(profile_))) {
wallpaper_receiver_.ReportBadMessage("Invalid request to set wallpaper");
return;
}
if (pending_select_google_photos_photo_callback_) {
std::move(pending_select_google_photos_photo_callback_).Run(false);
}
pending_select_google_photos_photo_callback_ = std::move(callback);
SetMinimizedWindowStateForPreview(preview_mode);
WallpaperControllerClientImpl* client = WallpaperControllerClientImpl::Get();
DCHECK(client);
client->RecordWallpaperSourceUMA(ash::WallpaperType::kOnceGooglePhotos);
wallpaper_controller->SetGooglePhotosWallpaper(
ash::GooglePhotosWallpaperParams(GetAccountId(profile_), id,
/*daily_refresh_enabled=*/false, layout,
preview_mode,
/*dedup_key=*/std::nullopt),
base::BindOnce(&PersonalizationAppWallpaperProviderImpl::
OnGooglePhotosWallpaperSelected,
backend_weak_ptr_factory_.GetWeakPtr()));
}
void PersonalizationAppWallpaperProviderImpl::SelectGooglePhotosAlbum(
const std::string& album_id,
SelectGooglePhotosAlbumCallback callback) {
if (!is_google_photos_enterprise_enabled_) {
wallpaper_receiver_.ReportBadMessage(
"Rejected attempt to set Google Photos wallpaper while disabled via "
"enterprise setting.");
return;
}
auto* wallpaper_controller = WallpaperController::Get();
DCHECK(wallpaper_controller);
if (!wallpaper_controller->CanSetUserWallpaper(GetAccountId(profile_))) {
wallpaper_receiver_.ReportBadMessage(
"Invalid request to select google photos album");
return;
}
if (pending_set_daily_refresh_callback_) {
std::move(pending_set_daily_refresh_callback_).Run(/*success=*/false);
}
WallpaperControllerClientImpl* client = WallpaperControllerClientImpl::Get();
DCHECK(client);
client->RecordWallpaperSourceUMA(ash::WallpaperType::kDailyGooglePhotos);
bool force_refresh = true;
if (album_id.empty()) {
// Empty |album_id| means disabling daily refresh.
force_refresh = false;
} else {
// Only force refresh if the album does not contain the current wallpaper
// image.
const auto& it = album_id_dedup_key_map_.find(album_id);
std::optional<ash::WallpaperInfo> info =
wallpaper_controller->GetWallpaperInfoForAccountId(
GetAccountId(profile_));
if (info.has_value() && info->dedup_key.has_value()) {
force_refresh =
it == album_id_dedup_key_map_.end() ||
it->second.find(info->dedup_key.value()) == it->second.end();
}
}
DVLOG(1) << __func__ << " force_refresh=" << force_refresh;
wallpaper_controller->SetGooglePhotosDailyRefreshAlbumId(
GetAccountId(profile_), album_id);
if (force_refresh) {
pending_set_daily_refresh_callback_ = std::move(callback);
wallpaper_controller->UpdateDailyRefreshWallpaper(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnDailyRefreshWallpaperForced,
backend_weak_ptr_factory_.GetWeakPtr()));
return;
}
std::move(callback).Run(/*success=*/true);
// Trigger a `NotifyWallpaperChanged` to clear loading state in
// Personalization App so it can't get stuck.
OnWallpaperResized();
}
void PersonalizationAppWallpaperProviderImpl::
GetGooglePhotosDailyRefreshAlbumId(
GetGooglePhotosDailyRefreshAlbumIdCallback callback) {
auto* controller = WallpaperController::Get();
std::move(callback).Run(
controller->GetGooglePhotosDailyRefreshAlbumId(GetAccountId(profile_)));
}
void PersonalizationAppWallpaperProviderImpl::SetCurrentWallpaperLayout(
ash::WallpaperLayout layout) {
WallpaperController::Get()->UpdateCurrentWallpaperLayout(
GetAccountId(profile_), layout);
}
void PersonalizationAppWallpaperProviderImpl::SetDailyRefreshCollectionId(
const std::string& collection_id,
SetDailyRefreshCollectionIdCallback callback) {
if (pending_set_daily_refresh_callback_) {
std::move(pending_set_daily_refresh_callback_).Run(/*success=*/false);
}
auto* wallpaper_controller = WallpaperController::Get();
DCHECK(wallpaper_controller);
if (!wallpaper_controller->CanSetUserWallpaper(GetAccountId(profile_))) {
wallpaper_receiver_.ReportBadMessage("Invalid request to set wallpaper");
return;
}
if (collection_id == wallpaper_constants::kTimeOfDayWallpaperCollectionId) {
wallpaper_receiver_.ReportBadMessage("Unsupported wallpaper collection");
return;
}
const AccountId account_id = GetAccountId(profile_);
wallpaper_controller->SetDailyRefreshCollectionId(account_id, collection_id);
std::optional<ash::WallpaperInfo> info =
wallpaper_controller->GetWallpaperInfoForAccountId(account_id);
DCHECK(info);
if (collection_id.empty()) {
// Daily refresh is disabled.
std::move(callback).Run(/*success=*/info &&
info->type != WallpaperType::kDaily);
return;
}
bool force_refresh = !info->unit_id.has_value();
if (info->unit_id.has_value()) {
const auto& it = image_unit_id_map_.find(info->unit_id.value());
// Only force refresh if the current wallpaper image does not belong to
// this collection.
force_refresh = it == image_unit_id_map_.end() || it->second.empty() ||
it->second[0].collection_id != collection_id;
}
DVLOG(1) << __func__ << " info=" << info.value()
<< " collection_id=" << collection_id
<< " force_refresh=" << force_refresh;
if (force_refresh) {
pending_set_daily_refresh_callback_ = std::move(callback);
wallpaper_controller->UpdateDailyRefreshWallpaper(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnDailyRefreshWallpaperForced,
weak_ptr_factory_.GetWeakPtr()));
return;
}
std::move(callback).Run(/*success=*/true);
}
void PersonalizationAppWallpaperProviderImpl::GetDailyRefreshCollectionId(
GetDailyRefreshCollectionIdCallback callback) {
auto* controller = WallpaperController::Get();
std::move(callback).Run(
controller->GetDailyRefreshCollectionId(GetAccountId(profile_)));
}
void PersonalizationAppWallpaperProviderImpl::UpdateDailyRefreshWallpaper(
UpdateDailyRefreshWallpaperCallback callback) {
if (pending_update_daily_refresh_wallpaper_callback_) {
std::move(pending_update_daily_refresh_wallpaper_callback_)
.Run(/*success=*/false);
}
pending_update_daily_refresh_wallpaper_callback_ = std::move(callback);
auto* wallpaper_controller = WallpaperController::Get();
std::optional<ash::WallpaperInfo> info =
wallpaper_controller->GetWallpaperInfoForAccountId(
GetAccountId(profile_));
DCHECK(info);
DCHECK(info->type == WallpaperType::kDaily ||
info->type == WallpaperType::kDailyGooglePhotos);
auto* client = WallpaperControllerClientImpl::Get();
client->RecordWallpaperSourceUMA(info->type);
wallpaper_controller->UpdateDailyRefreshWallpaper(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::OnDailyRefreshWallpaperUpdated,
backend_weak_ptr_factory_.GetWeakPtr()));
}
void PersonalizationAppWallpaperProviderImpl::IsInTabletMode(
IsInTabletModeCallback callback) {
std::move(callback).Run(display::Screen::GetScreen()->InTabletMode());
}
void PersonalizationAppWallpaperProviderImpl::ConfirmPreviewWallpaper() {
// Confirm the preview wallpaper before restoring the other windows. In tablet
// splitscreen, this prevents `WallpaperController::OnOverviewModeWillStart`
// from triggering first, which leads to preview wallpaper getting canceled
// before it gets confirmed (b/289133203).
WallpaperControllerClientImpl::Get()->ConfirmPreviewWallpaper(profile_);
}
void PersonalizationAppWallpaperProviderImpl::CancelPreviewWallpaper() {
WallpaperControllerClientImpl::Get()->CancelPreviewWallpaper(profile_);
}
void PersonalizationAppWallpaperProviderImpl::
ShouldShowTimeOfDayWallpaperDialog(
ShouldShowTimeOfDayWallpaperDialogCallback callback) {
std::move(callback).Run(
features::IsTimeOfDayWallpaperEnabled() &&
contextual_tooltip::ShouldShowNudge(
profile_->GetPrefs(),
contextual_tooltip::TooltipType::kTimeOfDayWallpaperDialog,
/*recheck_delay=*/nullptr));
}
wallpaper_handlers::GooglePhotosAlbumsFetcher*
PersonalizationAppWallpaperProviderImpl::
GetOrCreateGooglePhotosAlbumsFetcher() {
if (!google_photos_albums_fetcher_) {
google_photos_albums_fetcher_ =
wallpaper_fetcher_delegate_->CreateGooglePhotosAlbumsFetcher(profile_);
}
return google_photos_albums_fetcher_.get();
}
wallpaper_handlers::GooglePhotosSharedAlbumsFetcher*
PersonalizationAppWallpaperProviderImpl::
GetOrCreateGooglePhotosSharedAlbumsFetcher() {
if (!google_photos_shared_albums_fetcher_) {
google_photos_shared_albums_fetcher_ =
wallpaper_fetcher_delegate_->CreateGooglePhotosSharedAlbumsFetcher(
profile_);
}
return google_photos_shared_albums_fetcher_.get();
}
wallpaper_handlers::GooglePhotosEnabledFetcher*
PersonalizationAppWallpaperProviderImpl::
GetOrCreateGooglePhotosEnabledFetcher() {
if (!google_photos_enabled_fetcher_) {
google_photos_enabled_fetcher_ =
wallpaper_fetcher_delegate_->CreateGooglePhotosEnabledFetcher(profile_);
}
return google_photos_enabled_fetcher_.get();
}
wallpaper_handlers::GooglePhotosPhotosFetcher*
PersonalizationAppWallpaperProviderImpl::
GetOrCreateGooglePhotosPhotosFetcher() {
if (!google_photos_photos_fetcher_) {
google_photos_photos_fetcher_ =
wallpaper_fetcher_delegate_->CreateGooglePhotosPhotosFetcher(profile_);
}
return google_photos_photos_fetcher_.get();
}
void PersonalizationAppWallpaperProviderImpl::OnFetchCollections(
bool success,
const std::vector<backdrop::Collection>& collections) {
DCHECK(wallpaper_collection_info_fetcher_);
DCHECK(!pending_collections_callbacks_.empty());
std::optional<std::vector<backdrop::Collection>> result;
if (success && !collections.empty()) {
result = std::move(collections);
}
for (auto& callback : pending_collections_callbacks_) {
std::move(callback).Run(result);
}
pending_collections_callbacks_.clear();
wallpaper_collection_info_fetcher_.reset();
}
void PersonalizationAppWallpaperProviderImpl::OnFetchCollectionImages(
FetchImagesForCollectionCallback callback,
std::unique_ptr<wallpaper_handlers::BackdropImageInfoFetcher> fetcher,
bool success,
const std::string& collection_id,
const std::vector<backdrop::Image>& images) {
std::optional<std::vector<backdrop::Image>> result;
if (success && !images.empty()) {
// Do first pass to clear all unit_id associated with the images.
base::ranges::for_each(images, [&](auto& proto_image) {
image_unit_id_map_.erase(proto_image.unit_id());
});
// Do second pass to repopulate the map with fresh data.
base::ranges::for_each(images, [&](auto& proto_image) {
if (proto_image.has_asset_id() && proto_image.has_unit_id() &&
proto_image.has_image_url()) {
image_unit_id_map_[proto_image.unit_id()].push_back(
ImageInfo(GURL(proto_image.image_url()), collection_id,
proto_image.asset_id(), proto_image.unit_id(),
proto_image.has_image_type()
? proto_image.image_type()
: backdrop::Image::IMAGE_TYPE_UNKNOWN));
}
});
result = std::move(images);
}
std::move(callback).Run(std::move(result));
}
void PersonalizationAppWallpaperProviderImpl::OnFetchGooglePhotosEnabled(
FetchGooglePhotosEnabledCallback callback,
ash::personalization_app::mojom::GooglePhotosEnablementState state) {
is_google_photos_enterprise_enabled_ =
state ==
ash::personalization_app::mojom::GooglePhotosEnablementState::kEnabled;
std::move(callback).Run(state);
}
void PersonalizationAppWallpaperProviderImpl::OnFetchGooglePhotosPhotos(
std::optional<std::string> album_id,
FetchGooglePhotosPhotosCallback callback,
mojo::StructPtr<mojom::FetchGooglePhotosPhotosResponse> response) {
if (!album_id || !response || response.is_null()) {
// Skip processing |album_id_dedup_key_map_| if there is no info on album or
// response is invalid.
std::move(callback).Run(std::move(response));
return;
}
// Process |album_id_dedup_key_map_|.
auto& photos = response->photos;
if (photos.has_value()) {
auto& photos_val = photos.value();
std::set<std::string> dedup_keys;
for (auto& photo : photos_val) {
if (photo->dedup_key.has_value()) {
dedup_keys.insert(photo->dedup_key.value());
}
}
album_id_dedup_key_map_.insert({album_id.value(), std::move(dedup_keys)});
}
std::move(callback).Run(std::move(response));
}
void PersonalizationAppWallpaperProviderImpl::OnGetDefaultImage(
GetDefaultImageThumbnailCallback callback,
const gfx::ImageSkia& image) {
if (image.isNull()) {
// Do not call |mojom::ReportBadMessage| here. The message is valid, but the
// file may be corrupt or unreadable.
std::move(callback).Run(GURL());
return;
}
gfx::ImageSkia resized =
WallpaperResizer::GetResizedImage(image, kLocalImageThumbnailSizeDip);
std::move(callback).Run(GetBitmapJpegDataUrl(*resized.bitmap()));
}
void PersonalizationAppWallpaperProviderImpl::OnGetLocalImages(
GetLocalImagesCallback callback,
const std::vector<base::FilePath>& images) {
local_images_ = std::set<base::FilePath>(images.begin(), images.end());
std::move(callback).Run(images);
}
void PersonalizationAppWallpaperProviderImpl::OnGetLocalImageThumbnail(
GetLocalImageThumbnailCallback callback,
const SkBitmap* bitmap,
base::File::Error error) {
if (error != base::File::Error::FILE_OK) {
// Do not call |mojom::ReportBadMessage| here. The message is valid, but
// the file may be corrupt or unreadable.
std::move(callback).Run(GURL());
return;
}
std::move(callback).Run(GetBitmapJpegDataUrl(*bitmap));
}
void PersonalizationAppWallpaperProviderImpl::OnOnlineWallpaperSelected(
bool success) {
DCHECK(pending_select_wallpaper_callback_);
std::move(pending_select_wallpaper_callback_).Run(success);
}
void PersonalizationAppWallpaperProviderImpl::OnGooglePhotosWallpaperSelected(
bool success) {
DCHECK(pending_select_google_photos_photo_callback_);
std::move(pending_select_google_photos_photo_callback_).Run(success);
}
void PersonalizationAppWallpaperProviderImpl::OnLocalImageSelected(
bool success) {
DCHECK(pending_select_local_image_callback_);
std::move(pending_select_local_image_callback_).Run(success);
}
void PersonalizationAppWallpaperProviderImpl::OnDailyRefreshWallpaperUpdated(
bool success) {
DCHECK(pending_update_daily_refresh_wallpaper_callback_);
std::move(pending_update_daily_refresh_wallpaper_callback_).Run(success);
}
void PersonalizationAppWallpaperProviderImpl::OnDailyRefreshWallpaperForced(
bool success) {
if (pending_set_daily_refresh_callback_) {
std::move(pending_set_daily_refresh_callback_).Run(success);
}
}
void PersonalizationAppWallpaperProviderImpl::FindAttribution(
const ash::WallpaperInfo& info,
const std::optional<std::vector<backdrop::Collection>>& collections) {
DCHECK(!wallpaper_attribution_info_fetcher_);
if (!collections.has_value() || collections->empty()) {
const std::string key = GetOnlineWallpaperKey(info);
NotifyWallpaperChanged(
ash::personalization_app::mojom::CurrentWallpaper::New(
info.layout, info.type, key,
/*description_title=*/std::string(),
/*description_content=*/std::string()));
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
std::vector<std::string>(), key));
return;
}
std::size_t current_index = 0;
wallpaper_attribution_info_fetcher_ =
wallpaper_fetcher_delegate_->CreateBackdropImageInfoFetcher(
collections->at(current_index).collection_id());
wallpaper_attribution_info_fetcher_->Start(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::FindImageMetadataInCollection,
attribution_weak_ptr_factory_.GetWeakPtr(), info, current_index,
collections));
}
void PersonalizationAppWallpaperProviderImpl::FindImageMetadataInCollection(
const ash::WallpaperInfo& info,
std::size_t current_index,
const std::optional<std::vector<backdrop::Collection>>& collections,
bool success,
const std::string& collection_id,
const std::vector<backdrop::Image>& images) {
DCHECK(wallpaper_attribution_info_fetcher_);
const backdrop::Image* backend_image = nullptr;
if (success && !images.empty()) {
for (const auto& proto_image : images) {
if (!proto_image.has_image_url() || !proto_image.has_unit_id()) {
break;
}
bool is_same_unit_id = info.unit_id.has_value() &&
proto_image.unit_id() == info.unit_id.value();
bool is_same_url = info.location.rfind(proto_image.image_url(), 0) == 0;
if (is_same_url) {
backend_image = &proto_image;
break;
}
if (is_same_unit_id) {
backend_image = &proto_image;
}
}
}
if (backend_image) {
NotifyWallpaperChanged(
ash::personalization_app::mojom::CurrentWallpaper::New(
info.layout, info.type,
/*key=*/base::NumberToString(backend_image->unit_id()),
backend_image->description_title(),
backend_image->description_content()));
std::vector<std::string> attributions;
for (const auto& attr : backend_image->attribution()) {
attributions.push_back(attr.text());
}
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
attributions, base::NumberToString(backend_image->unit_id())));
wallpaper_attribution_info_fetcher_.reset();
return;
}
++current_index;
if (current_index >= collections->size()) {
const std::string key = GetOnlineWallpaperKey(info);
NotifyWallpaperChanged(
ash::personalization_app::mojom::CurrentWallpaper::New(
info.layout, info.type, key,
/*description_title=*/std::string(),
/*description_content=*/std::string()));
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
std::vector<std::string>(), key));
wallpaper_attribution_info_fetcher_.reset();
return;
}
auto fetcher = wallpaper_fetcher_delegate_->CreateBackdropImageInfoFetcher(
collections->at(current_index).collection_id());
fetcher->Start(base::BindOnce(
&PersonalizationAppWallpaperProviderImpl::FindImageMetadataInCollection,
attribution_weak_ptr_factory_.GetWeakPtr(), info, current_index,
collections));
// resetting the previous fetcher last because the current method is bound
// to a callback owned by the previous fetcher.
wallpaper_attribution_info_fetcher_ = std::move(fetcher);
}
void PersonalizationAppWallpaperProviderImpl::FindSeaPenWallpaperAttribution(
const uint32_t id) {
auto* sea_pen_wallpaper_manager = SeaPenWallpaperManager::GetInstance();
DCHECK(sea_pen_wallpaper_manager);
sea_pen_wallpaper_manager->GetImageAndMetadata(
GetAccountId(profile_), id,
base::BindOnce(&PersonalizationAppWallpaperProviderImpl::
SendSeaPenWallpaperAttribution,
weak_ptr_factory_.GetWeakPtr(), id));
}
void PersonalizationAppWallpaperProviderImpl::SendSeaPenWallpaperAttribution(
const uint32_t id,
const gfx::ImageSkia& image,
mojom::RecentSeaPenImageInfoPtr sea_pen_metadata) {
if (sea_pen_metadata.is_null()) {
LOG(WARNING) << __func__ << " unable to get metadata";
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
std::vector<std::string>(), base::NumberToString(id)));
return;
}
std::vector<std::string> attribution;
const std::string query_str = GetQueryString(sea_pen_metadata);
if (!query_str.empty()) {
attribution.push_back(std::move(query_str));
}
attribution.push_back(
l10n_util::GetStringUTF8(IDS_SEA_PEN_POWERED_BY_GOOGLE_AI));
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
attribution, base::NumberToString(id)));
}
void PersonalizationAppWallpaperProviderImpl::SendGooglePhotosAttribution(
const ash::WallpaperInfo& info,
mojo::StructPtr<ash::personalization_app::mojom::GooglePhotosPhoto> photo,
bool success) {
// If the fetch for |photo| succeeded but |photo| does not exist, that means
// it has been removed from the user's library. When this occurs, the user's
// wallpaper should be either (a) reset to default or (b) updated to a new
// photo from the same collection depending on whether daily refresh is
// enabled.
if (success && !photo) {
if (info.type == WallpaperType::kOnceGooglePhotos) {
SelectDefaultImage(/*callback=*/base::DoNothing());
} else if (info.type == WallpaperType::kDailyGooglePhotos) {
UpdateDailyRefreshWallpaper(/*callback=*/base::DoNothing());
} else {
NOTREACHED_IN_MIGRATION();
}
return;
}
// NOTE: Old clients may not support |dedup_key| when setting Google Photos
// wallpaper, so use |location| in such cases for backwards compatibility.
NotifyWallpaperChanged(ash::personalization_app::mojom::CurrentWallpaper::New(
info.layout, info.type,
/*key=*/info.dedup_key.value_or(info.location),
/*description_title=*/std::string(),
/*description_content=*/std::string()));
std::vector<std::string> attribution;
if (!photo.is_null()) {
attribution.push_back(photo->name);
}
NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttribution::New(
attribution, info.dedup_key.value_or(info.location)));
}
void PersonalizationAppWallpaperProviderImpl::SetMinimizedWindowStateForPreview(
bool preview_mode) {
auto* wallpaper_controller = WallpaperController::Get();
const std::string& user_id_hash = GetUser(profile_)->username_hash();
if (preview_mode) {
wallpaper_controller->MinimizeInactiveWindows(user_id_hash);
} else {
wallpaper_controller->RestoreMinimizedWindows(user_id_hash);
}
}
void PersonalizationAppWallpaperProviderImpl::NotifyAttributionChanged(
ash::personalization_app::mojom::CurrentAttributionPtr attribution) {
DCHECK(wallpaper_observer_remote_.is_bound());
wallpaper_observer_remote_->OnAttributionChanged(std::move(attribution));
}
void PersonalizationAppWallpaperProviderImpl::NotifyWallpaperChanged(
ash::personalization_app::mojom::CurrentWallpaperPtr current_wallpaper) {
DCHECK(wallpaper_observer_remote_.is_bound());
wallpaper_observer_remote_->OnWallpaperChanged(std::move(current_wallpaper));
}
} // namespace ash::personalization_app