// Copyright 2017 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/ui/ash/wallpaper/wallpaper_controller_client_impl.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/wallpaper/online_wallpaper_params.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller.h"
#include "ash/public/cpp/window_backdrop.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
#include "ash/webui/personalization_app/personalization_app_url_constants.h"
#include "ash/webui/personalization_app/proto/backdrop_wallpaper.pb.h"
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/containers/extend.h"
#include "base/functional/bind.h"
#include "base/hash/hash.h"
#include "base/hash/sha1.h"
#include "base/metrics/histogram_functions.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/ash/customization/customization_wallpaper_util.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/file_manager/volume.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/login/wizard_controller.h"
#include "chrome/browser/ash/wallpaper/wallpaper_drivefs_delegate_impl.h"
#include "chrome/browser/ash/wallpaper_handlers/wallpaper_fetcher_delegate.h"
#include "chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/contents_web_view.h"
#include "chrome/browser/ui/webui/ash/settings/pref_names.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/account_id/account_id.h"
#include "components/policy/core/common/device_local_account_type.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/session_manager/core/session_manager.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/render_widget_host_view.h"
#include "ui/display/screen.h"
#include "url/gurl.h"
using ::ash::ProfileHelper;
using file_manager::VolumeManager;
using session_manager::SessionManager;
using wallpaper_handlers::BackdropSurpriseMeImageFetcher;
namespace {
// Known user keys.
const char kWallpaperFilesId[] = "wallpaper-files-id";
WallpaperControllerClientImpl* g_wallpaper_controller_client_instance = nullptr;
bool IsKnownUser(const AccountId& account_id) {
return user_manager::UserManager::Get()->IsKnownUser(account_id);
}
// Returns the type of the user with the specified |id| or kRegular.
user_manager::UserType GetUserType(const AccountId& id) {
if (user_manager::UserManager::IsInitialized()) {
if (auto* user = user_manager::UserManager::Get()->FindUser(id))
return user->GetType();
}
// TODO(b/258750657): Convert this to a DCHECK when tests are fixed.
LOG(WARNING) << "No matching user. This should only happen in tests.";
// Unit tests may not have a UserManager.
return user_manager::UserType::kRegular;
}
// This has once been copied from
// brillo::cryptohome::home::SanitizeUserName(username) to be used for
// wallpaper identification purpose only.
//
// Historic note: We need some way to identify users wallpaper files in
// the device filesystem. Historically User::username_hash() was used for this
// purpose, but it has two caveats:
// 1. username_hash() is defined only after user has logged in.
// 2. If cryptohome identifier changes, username_hash() will also change,
// and we may lose user => wallpaper files mapping at that point.
// So this function gives WallpaperManager independent hashing method to break
// this dependency.
std::string HashWallpaperFilesIdStr(std::string_view files_id_unhashed) {
ash::SystemSaltGetter* salt_getter = ash::SystemSaltGetter::Get();
DCHECK(salt_getter);
// System salt must be defined at this point.
const ash::SystemSaltGetter::RawSalt* salt = salt_getter->GetRawSalt();
if (!salt)
LOG(FATAL) << "WallpaperManager HashWallpaperFilesIdStr(): no salt!";
std::vector<uint8_t> data = *salt;
// Note: The original code in https://codereview.chromium.org/1886653002/
// presumably meant to lowercase the input string before hashing, but it did
// not.
base::Extend(data, base::as_byte_span(files_id_unhashed));
return base::ToLowerASCII(base::HexEncode(base::SHA1Hash(data)));
}
// Returns true if wallpaper files id can be returned successfully.
bool CanGetFilesId() {
return ash::SystemSaltGetter::IsInitialized() &&
ash::SystemSaltGetter::Get()->GetRawSalt();
}
void GetFilesIdSaltReady(
const AccountId& account_id,
base::OnceCallback<void(const std::string&)> files_id_callback) {
DCHECK(CanGetFilesId());
user_manager::KnownUser known_user(g_browser_process->local_state());
if (const std::string* stored_value =
known_user.FindStringPath(account_id, kWallpaperFilesId)) {
std::move(files_id_callback).Run(*stored_value);
return;
}
const std::string wallpaper_files_id =
HashWallpaperFilesIdStr(account_id.GetUserEmail());
if (known_user.UserExists(account_id)) {
// This is async call, so during the operation (i.e. waiting for salt
// is updated via D-Bus), the user may be deleted. Do not cache for
// such cases, but still returns the value.
known_user.SetStringPref(account_id, kWallpaperFilesId, wallpaper_files_id);
}
std::move(files_id_callback).Run(wallpaper_files_id);
}
// Returns true if |users| contains users other than device local accounts.
bool HasNonDeviceLocalAccounts(const user_manager::UserList& users) {
for (const user_manager::User* user : users) {
if (!policy::IsDeviceLocalAccountUser(
user->GetAccountId().GetUserEmail())) {
return true;
}
}
return false;
}
// Returns the first public session user found in |users|, or null if there's
// none.
user_manager::User* FindPublicSession(const user_manager::UserList& users) {
for (size_t i = 0; i < users.size(); ++i) {
if (users[i]->GetType() == user_manager::UserType::kPublicAccount) {
return users[i];
}
}
return nullptr;
}
} // namespace
WallpaperControllerClientImpl::WallpaperControllerClientImpl(
std::unique_ptr<wallpaper_handlers::WallpaperFetcherDelegate>
wallpaper_fetcher_delegate)
: wallpaper_fetcher_delegate_(std::move(wallpaper_fetcher_delegate)) {
local_state_ = g_browser_process->local_state();
show_user_names_on_signin_subscription_ =
ash::CrosSettings::Get()->AddSettingsObserver(
ash::kAccountsPrefShowUserNamesOnSignIn,
base::BindRepeating(
&WallpaperControllerClientImpl::ShowWallpaperOnLoginScreen,
weak_factory_.GetWeakPtr()));
DCHECK(!g_wallpaper_controller_client_instance);
g_wallpaper_controller_client_instance = this;
SessionManager* session_manager = SessionManager::Get();
// SessionManager might not exist in unit tests.
if (session_manager)
session_observation_.Observe(session_manager);
if (user_manager::UserManager::IsInitialized()) {
user_manager_observation_.Observe(user_manager::UserManager::Get());
} else {
CHECK_IS_TEST();
}
}
WallpaperControllerClientImpl::~WallpaperControllerClientImpl() {
wallpaper_controller_->SetClient(nullptr);
weak_factory_.InvalidateWeakPtrs();
DCHECK_EQ(this, g_wallpaper_controller_client_instance);
g_wallpaper_controller_client_instance = nullptr;
}
void WallpaperControllerClientImpl::Init() {
pref_registrar_.Init(local_state_);
pref_registrar_.Add(
prefs::kDeviceWallpaperImageFilePath,
base::BindRepeating(
&WallpaperControllerClientImpl::DeviceWallpaperImageFilePathChanged,
weak_factory_.GetWeakPtr()));
wallpaper_controller_ = ash::WallpaperController::Get();
InitController();
}
void WallpaperControllerClientImpl::InitForTesting(
ash::WallpaperController* controller) {
wallpaper_controller_ = controller;
InitController();
}
void WallpaperControllerClientImpl::SetWallpaperFetcherDelegateForTesting(
std::unique_ptr<wallpaper_handlers::WallpaperFetcherDelegate>
wallpaper_fetcher_delegate) {
wallpaper_fetcher_delegate_.swap(wallpaper_fetcher_delegate);
}
void WallpaperControllerClientImpl::SetInitialWallpaper() {
// Apply device customization.
namespace customization_util = ash::customization_wallpaper_util;
if (customization_util::ShouldUseCustomizedDefaultWallpaper()) {
base::FilePath customized_default_small_path;
base::FilePath customized_default_large_path;
if (customization_util::GetCustomizedDefaultWallpaperPaths(
&customized_default_small_path, &customized_default_large_path)) {
wallpaper_controller_->SetCustomizedDefaultWallpaperPaths(
customized_default_small_path, customized_default_large_path);
}
}
// Guest wallpaper should be initialized when guest logs in.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kGuestSession)) {
return;
}
// Do not set wallpaper in tests.
if (ash::WizardController::IsZeroDelayEnabled())
return;
// Show the wallpaper of the active user during an user session.
if (user_manager::UserManager::Get()->IsUserLoggedIn()) {
ShowUserWallpaper(
user_manager::UserManager::Get()->GetActiveUser()->GetAccountId());
return;
}
// Show a wallpaper during OOBE.
if (SessionManager::Get()->session_state() ==
session_manager::SessionState::OOBE) {
wallpaper_controller_->ShowOobeWallpaper();
return;
}
ShowWallpaperOnLoginScreen();
}
// static
WallpaperControllerClientImpl* WallpaperControllerClientImpl::Get() {
return g_wallpaper_controller_client_instance;
}
void WallpaperControllerClientImpl::SetPolicyWallpaper(
const AccountId& account_id,
std::unique_ptr<std::string> data) {
if (!data || !IsKnownUser(account_id))
return;
wallpaper_controller_->SetPolicyWallpaper(account_id, GetUserType(account_id),
*data);
}
bool WallpaperControllerClientImpl::SetThirdPartyWallpaper(
const AccountId& account_id,
const std::string& file_name,
ash::WallpaperLayout layout,
const gfx::ImageSkia& image) {
RecordWallpaperSourceUMA(ash::WallpaperType::kThirdParty);
return IsKnownUser(account_id) &&
wallpaper_controller_->SetThirdPartyWallpaper(account_id, file_name,
layout, image);
}
void WallpaperControllerClientImpl::ShowUserWallpaper(
const AccountId& account_id) {
if (IsKnownUser(account_id)) {
user_manager::UserType user_type = GetUserType(account_id);
wallpaper_controller_->ShowUserWallpaper(account_id, user_type);
}
}
void WallpaperControllerClientImpl::RemoveUserWallpaper(
const AccountId& account_id,
base::OnceClosure on_removed) {
if (!IsKnownUser(account_id)) {
std::move(on_removed).Run();
return;
}
wallpaper_controller_->RemoveUserWallpaper(account_id, std::move(on_removed));
}
void WallpaperControllerClientImpl::RemovePolicyWallpaper(
const AccountId& account_id) {
if (!IsKnownUser(account_id))
return;
wallpaper_controller_->RemovePolicyWallpaper(account_id);
}
void WallpaperControllerClientImpl::GetFilesId(
const AccountId& account_id,
base::OnceCallback<void(const std::string&)> files_id_callback) const {
ash::SystemSaltGetter::Get()->AddOnSystemSaltReady(base::BindOnce(
&GetFilesIdSaltReady, account_id, std::move(files_id_callback)));
}
bool WallpaperControllerClientImpl::IsWallpaperSyncEnabled(
const AccountId& account_id) const {
Profile* profile = ProfileHelper::Get()->GetProfileByAccountId(account_id);
if (!profile)
return false;
syncer::SyncService* sync_service =
SyncServiceFactory::GetForProfile(profile);
if (!sync_service)
return false;
syncer::SyncUserSettings* user_settings = sync_service->GetUserSettings();
return user_settings->IsSyncAllOsTypesEnabled() ||
profile->GetPrefs()->GetBoolean(
ash::settings::prefs::kSyncOsWallpaper);
}
void WallpaperControllerClientImpl::CancelPreviewWallpaper(Profile* profile) {
wallpaper_controller_->CancelPreviewWallpaper();
user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile);
wallpaper_controller_->RestoreMinimizedWindows(user->username_hash());
}
void WallpaperControllerClientImpl::ConfirmPreviewWallpaper(Profile* profile) {
wallpaper_controller_->ConfirmPreviewWallpaper();
user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile);
wallpaper_controller_->RestoreMinimizedWindows(user->username_hash());
}
void WallpaperControllerClientImpl::MakeTransparent(
content::WebContents* web_contents) {
// Disable the window backdrop that creates an opaque layer in tablet mode.
ash::WindowBackdrop* window_backdrop =
ash::WindowBackdrop::Get(web_contents->GetTopLevelNativeWindow());
window_backdrop->SetBackdropMode(
ash::WindowBackdrop::BackdropMode::kDisabled);
// Set transparency on the top level native window and tell the WM not to
// change it when window state changes.
aura::Window* top_level_window = web_contents->GetTopLevelNativeWindow();
top_level_window->SetProperty(::chromeos::kWindowManagerManagesOpacityKey,
false);
top_level_window->SetTransparent(true);
// Set the background color to transparent.
web_contents->GetRenderWidgetHostView()->SetBackgroundColor(
SK_ColorTRANSPARENT);
// Turn off the web contents background.
static_cast<ContentsWebView*>(BrowserView::GetBrowserViewForNativeWindow(
web_contents->GetTopLevelNativeWindow())
->contents_web_view())
->SetBackgroundVisible(false);
}
void WallpaperControllerClientImpl::MakeOpaque(
content::WebContents* web_contents) {
// Reversing `contents_web_view` is sufficient to make the view opaque,
// as `window_backdrop`, `top_level_window` and `web_contents` are not
// highly impactful to the animated theme change effect.
static_cast<ContentsWebView*>(BrowserView::GetBrowserViewForNativeWindow(
web_contents->GetTopLevelNativeWindow())
->contents_web_view())
->SetBackgroundVisible(true);
}
void WallpaperControllerClientImpl::OnVolumeMounted(
ash::MountError error_code,
const file_manager::Volume& volume) {
if (error_code != ash::MountError::kSuccess) {
return;
}
if (volume.type() != file_manager::VolumeType::VOLUME_TYPE_GOOGLE_DRIVE) {
return;
}
Profile* profile = ProfileManager::GetActiveUserProfile();
CHECK(profile);
VolumeManager* volume_manager = VolumeManager::Get(profile);
// Volume ID is based on the mount path, which for drive is based on the
// account_id.
if (!volume_manager->FindVolumeById(volume.volume_id())) {
return;
}
user_manager::User* user = user_manager::UserManager::Get()->GetActiveUser();
CHECK(user);
wallpaper_controller_->SyncLocalAndRemotePrefs(user->GetAccountId());
}
void WallpaperControllerClientImpl::OnUserProfileLoaded(
const AccountId& account_id) {
wallpaper_controller_->SyncLocalAndRemotePrefs(account_id);
ObserveVolumeManagerForAccountId(account_id);
}
void WallpaperControllerClientImpl::OnUserLoggedIn(
const user_manager::User& user) {
// For public account, it's possible that the user-policy controlled wallpaper
// was fetched/cleared at the login screen (while for a regular user it was
// always fetched/cleared inside a user session), in the case the user-policy
// controlled wallpaper was fetched/cleared but not updated in the login
// screen, we need to update the wallpaper after the public user logged in.
ShowUserWallpaper(user.GetAccountId());
}
void WallpaperControllerClientImpl::DeviceWallpaperImageFilePathChanged() {
wallpaper_controller_->SetDevicePolicyWallpaperPath(
GetDeviceWallpaperImageFilePath());
}
void WallpaperControllerClientImpl::InitController() {
wallpaper_controller_->SetClient(this);
wallpaper_controller_->SetDriveFsDelegate(
std::make_unique<ash::WallpaperDriveFsDelegateImpl>());
base::FilePath user_data;
CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &user_data));
base::FilePath wallpapers;
CHECK(base::PathService::Get(chrome::DIR_CHROMEOS_WALLPAPERS, &wallpapers));
base::FilePath custom_wallpapers;
CHECK(base::PathService::Get(chrome::DIR_CHROMEOS_CUSTOM_WALLPAPERS,
&custom_wallpapers));
base::FilePath device_policy_wallpaper = GetDeviceWallpaperImageFilePath();
wallpaper_controller_->Init(user_data, wallpapers, custom_wallpapers,
device_policy_wallpaper);
}
void WallpaperControllerClientImpl::ShowWallpaperOnLoginScreen() {
if (user_manager::UserManager::Get()->IsUserLoggedIn())
return;
const user_manager::UserList& users =
user_manager::UserManager::Get()->GetUsers();
user_manager::User* public_session = FindPublicSession(users);
// Show the default signin wallpaper if there's no user to display.
if ((!ShouldShowUserNamesOnLogin() && !public_session) ||
!HasNonDeviceLocalAccounts(users)) {
wallpaper_controller_->ShowSigninWallpaper();
return;
}
// Normal boot, load user wallpaper.
const AccountId account_id = public_session ? public_session->GetAccountId()
: users[0]->GetAccountId();
ShowUserWallpaper(account_id);
}
void WallpaperControllerClientImpl::OpenWallpaperPicker() {
Profile* profile = ProfileManager::GetActiveUserProfile();
DCHECK(profile);
ash::SystemAppLaunchParams params;
params.url = GURL(
std::string(ash::personalization_app::kChromeUIPersonalizationAppURL) +
ash::personalization_app::kWallpaperSubpageRelativeUrl);
params.launch_source = apps::LaunchSource::kFromShelf;
ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::PERSONALIZATION,
params);
}
void WallpaperControllerClientImpl::FetchDailyRefreshWallpaper(
const std::string& collection_id,
DailyWallpaperUrlFetchedCallback callback) {
if (surprise_me_image_fetchers_.find(collection_id) ==
surprise_me_image_fetchers_.end()) {
surprise_me_image_fetchers_.insert(
{collection_id,
wallpaper_fetcher_delegate_->CreateBackdropSurpriseMeImageFetcher(
collection_id)});
}
surprise_me_image_fetchers_[collection_id]->Start(
base::BindOnce(&WallpaperControllerClientImpl::OnDailyImageInfoFetched,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void WallpaperControllerClientImpl::FetchImagesForCollection(
const std::string& collection_id,
FetchImagesForCollectionCallback callback) {
auto images_info_fetcher =
wallpaper_fetcher_delegate_->CreateBackdropImageInfoFetcher(
collection_id);
auto* images_info_fetcher_ptr = images_info_fetcher.get();
images_info_fetcher_ptr->Start(
base::BindOnce(&WallpaperControllerClientImpl::OnFetchImagesForCollection,
weak_factory_.GetWeakPtr(), std::move(callback),
std::move(images_info_fetcher)));
}
void WallpaperControllerClientImpl::FetchGooglePhotosPhoto(
const AccountId& account_id,
const std::string& id,
FetchGooglePhotosPhotoCallback callback) {
if (google_photos_photos_fetchers_.find(account_id) ==
google_photos_photos_fetchers_.end()) {
Profile* profile = ProfileHelper::Get()->GetProfileByAccountId(account_id);
google_photos_photos_fetchers_.insert(
{account_id,
wallpaper_fetcher_delegate_->CreateGooglePhotosPhotosFetcher(
profile)});
}
auto fetched_callback =
base::BindOnce(&WallpaperControllerClientImpl::OnGooglePhotosPhotoFetched,
weak_factory_.GetWeakPtr(), std::move(callback));
google_photos_photos_fetchers_[account_id]->AddRequestAndStartIfNecessary(
id, /*album_id=*/std::nullopt,
/*resume_token=*/std::nullopt, /*shuffle=*/false,
std::move(fetched_callback));
}
void WallpaperControllerClientImpl::FetchDailyGooglePhotosPhoto(
const AccountId& account_id,
const std::string& album_id,
FetchGooglePhotosPhotoCallback callback) {
if (google_photos_photos_fetchers_.find(account_id) ==
google_photos_photos_fetchers_.end()) {
Profile* profile = ProfileHelper::Get()->GetProfileByAccountId(account_id);
google_photos_photos_fetchers_.insert(
{account_id,
wallpaper_fetcher_delegate_->CreateGooglePhotosPhotosFetcher(
profile)});
}
auto fetched_callback = base::BindOnce(
&WallpaperControllerClientImpl::OnGooglePhotosDailyAlbumFetched,
weak_factory_.GetWeakPtr(), account_id, std::move(callback));
google_photos_photos_fetchers_[account_id]->AddRequestAndStartIfNecessary(
/*item_id=*/std::nullopt, album_id,
/*resume_token=*/std::nullopt, /*shuffle=*/true,
std::move(fetched_callback));
}
void WallpaperControllerClientImpl::FetchGooglePhotosAccessToken(
const AccountId& account_id,
FetchGooglePhotosAccessTokenCallback callback) {
wallpaper_fetcher_delegate_->FetchGooglePhotosAccessToken(
account_id, std::move(callback));
}
bool WallpaperControllerClientImpl::ShouldShowUserNamesOnLogin() const {
bool show_user_names = true;
ash::CrosSettings::Get()->GetBoolean(ash::kAccountsPrefShowUserNamesOnSignIn,
&show_user_names);
return show_user_names;
}
base::FilePath
WallpaperControllerClientImpl::GetDeviceWallpaperImageFilePath() {
return base::FilePath(
local_state_->GetString(prefs::kDeviceWallpaperImageFilePath));
}
void WallpaperControllerClientImpl::OnDailyImageInfoFetched(
DailyWallpaperUrlFetchedCallback callback,
bool success,
const backdrop::Image& image,
const std::string& next_resume_token) {
std::move(callback).Run(success, std::move(image));
}
void WallpaperControllerClientImpl::OnFetchImagesForCollection(
FetchImagesForCollectionCallback callback,
std::unique_ptr<wallpaper_handlers::BackdropImageInfoFetcher> fetcher,
bool success,
const std::string& collection_id,
const std::vector<backdrop::Image>& images) {
std::move(callback).Run(success, std::move(images));
}
void WallpaperControllerClientImpl::OnGooglePhotosPhotoFetched(
FetchGooglePhotosPhotoCallback callback,
ash::personalization_app::mojom::FetchGooglePhotosPhotosResponsePtr
response) {
// If we have a `GooglePhotosPhoto`, pass that along. Otherwise, indicate to
// `callback` whether the the request succeeded or failed, since `callback`
// can take action if the `GooglePhotosPhoto` with the given id has been
// deleted.
if (response->photos.has_value() && response->photos.value().size() == 1) {
std::move(callback).Run(std::move(response->photos.value()[0]),
/*success=*/true);
} else {
std::move(callback).Run(nullptr, /*success=*/response->photos.has_value());
}
}
void WallpaperControllerClientImpl::OnGooglePhotosDailyAlbumFetched(
const AccountId& account_id,
FetchGooglePhotosPhotoCallback callback,
ash::personalization_app::mojom::FetchGooglePhotosPhotosResponsePtr
response) {
if (!response->photos.has_value() || response->photos.value().size() == 0) {
std::move(callback).Run(nullptr, /*success=*/response->photos.has_value());
return;
}
if (response->photos.value().size() == 1) {
std::move(callback).Run(std::move(response->photos.value()[0]),
/*success=*/true);
return;
}
// For small albums (n<12), we will repeat the photos in the same shuffled
// order indefinitely as we cycle through the cache. For large albums, the
// cache of size 10 makes sure we don't repeat photos within 10 refreshes.
auto& photos = response->photos.value();
int new_size = std::min(10, static_cast<int>(photos.size() - 1));
// Using a cache with the new size to populate with the stored data means
// that we will inherently drop the oldest values if the album has shrunk
// enough to warrant a change in cache size.
ash::WallpaperController::DailyGooglePhotosIdCache ids(new_size);
if (!wallpaper_controller_->GetDailyGooglePhotosWallpaperIdCache(account_id,
ids)) {
// This is expected the first time a user uses Google Photos wallpaper, but
// would be an error after that.
DVLOG(1) << "No cache of previously shown Google Photos ids found."
"Starting with an empty one.";
}
// TODO(b/229146895): Delete this shuffle once the Google Photos API has
// been updated to shuffle for us. This doesn't work for big albums (n>100).
base::RandomShuffle(photos.begin(), photos.end());
// Get the first photo from the shuffled set that is not in the LRU cache.
auto selected_itr = base::ranges::find_if(
photos,
[&ids](
const ash::personalization_app::mojom::GooglePhotosPhotoPtr& photo) {
return ids.Peek(base::PersistentHash(photo->id)) == ids.end();
});
DCHECK(selected_itr != photos.end());
auto& selected = *selected_itr;
ids.Put(base::PersistentHash(selected->id));
bool success = wallpaper_controller_->SetDailyGooglePhotosWallpaperIdCache(
account_id, ids);
DCHECK(success);
std::move(callback).Run(std::move(selected),
/*success=*/true);
}
void WallpaperControllerClientImpl::ObserveVolumeManagerForAccountId(
const AccountId& account_id) {
Profile* profile = ProfileHelper::Get()->GetProfileByAccountId(account_id);
CHECK(profile);
volume_manager_observation_.AddObservation(VolumeManager::Get(profile));
}
void WallpaperControllerClientImpl::RecordWallpaperSourceUMA(
const ash::WallpaperType type) {
base::UmaHistogramEnumeration("Ash.Wallpaper.Source2", type,
ash::WallpaperType::kCount);
}