// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/wallpaper_manager.h"
#include <vector>
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/file_util.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/worker_pool.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/login/startup_utils.h"
#include "chrome/browser/chromeos/login/user.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/skia_util.h"
using content::BrowserThread;
namespace {
// The amount of delay before starts to move custom wallpapers to the new place.
const int kMoveCustomWallpaperDelaySeconds = 30;
// Default quality for encoding wallpaper.
const int kDefaultEncodingQuality = 90;
// A dictionary pref that maps usernames to file paths to their wallpapers.
// Deprecated. Will remove this const char after done migration.
const char kUserWallpapers[] = "UserWallpapers";
const int kCacheWallpaperDelayMs = 500;
// A dictionary pref that maps usernames to wallpaper properties.
const char kUserWallpapersProperties[] = "UserWallpapersProperties";
// Names of nodes with info about wallpaper in |kUserWallpapersProperties|
// dictionary.
const char kNewWallpaperDateNodeName[] = "date";
const char kNewWallpaperLayoutNodeName[] = "layout";
const char kNewWallpaperFileNodeName[] = "file";
const char kNewWallpaperTypeNodeName[] = "type";
// File path suffix of the original custom wallpaper.
const char kOriginalCustomWallpaperSuffix[] = "_wallpaper";
// Maximum number of wallpapers cached by CacheUsersWallpapers().
const int kMaxWallpapersToCache = 3;
// For our scaling ratios we need to round positive numbers.
int RoundPositive(double x) {
return static_cast<int>(floor(x + 0.5));
}
// Returns custom wallpaper directory by appending corresponding |sub_dir|.
base::FilePath GetCustomWallpaperDir(const char* sub_dir) {
base::FilePath custom_wallpaper_dir;
CHECK(PathService::Get(chrome::DIR_CHROMEOS_CUSTOM_WALLPAPERS,
&custom_wallpaper_dir));
return custom_wallpaper_dir.Append(sub_dir);
}
bool MoveCustomWallpaperDirectory(const char* sub_dir,
const std::string& email,
const std::string& user_id_hash) {
base::FilePath base_path = GetCustomWallpaperDir(sub_dir);
base::FilePath to_path = base_path.Append(user_id_hash);
base::FilePath from_path = base_path.Append(email);
if (base::PathExists(from_path))
return base::Move(from_path, to_path);
return false;
}
} // namespace
namespace chromeos {
const char kWallpaperSequenceTokenName[] = "wallpaper-sequence";
const char kSmallWallpaperSuffix[] = "_small";
const char kLargeWallpaperSuffix[] = "_large";
const char kSmallWallpaperSubDir[] = "small";
const char kLargeWallpaperSubDir[] = "large";
const char kOriginalWallpaperSubDir[] = "original";
const char kThumbnailWallpaperSubDir[] = "thumb";
static WallpaperManager* g_wallpaper_manager = NULL;
// WallpaperManager, public: ---------------------------------------------------
// TestApi. For testing purpose
WallpaperManager::TestApi::TestApi(WallpaperManager* wallpaper_manager)
: wallpaper_manager_(wallpaper_manager) {
}
WallpaperManager::TestApi::~TestApi() {
}
base::FilePath WallpaperManager::TestApi::current_wallpaper_path() {
return wallpaper_manager_->current_wallpaper_path_;
}
// static
WallpaperManager* WallpaperManager::Get() {
if (!g_wallpaper_manager)
g_wallpaper_manager = new WallpaperManager();
return g_wallpaper_manager;
}
WallpaperManager::WallpaperManager()
: loaded_wallpapers_(0),
command_line_for_testing_(NULL),
should_cache_wallpaper_(false),
weak_factory_(this) {
registrar_.Add(this,
chrome::NOTIFICATION_LOGIN_USER_CHANGED,
content::NotificationService::AllSources());
registrar_.Add(this,
chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
content::NotificationService::AllSources());
registrar_.Add(this,
chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
content::NotificationService::AllSources());
sequence_token_ = BrowserThread::GetBlockingPool()->
GetNamedSequenceToken(kWallpaperSequenceTokenName);
task_runner_ = BrowserThread::GetBlockingPool()->
GetSequencedTaskRunnerWithShutdownBehavior(
sequence_token_,
base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
wallpaper_loader_ = new UserImageLoader(ImageDecoder::ROBUST_JPEG_CODEC,
task_runner_);
}
WallpaperManager::~WallpaperManager() {
// TODO(bshe): Lifetime of WallpaperManager needs more consideration.
// http://crbug.com/171694
DCHECK(!show_user_name_on_signin_subscription_);
ClearObsoleteWallpaperPrefs();
weak_factory_.InvalidateWeakPtrs();
}
void WallpaperManager::Shutdown() {
show_user_name_on_signin_subscription_.reset();
}
// static
void WallpaperManager::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kUsersWallpaperInfo);
registry->RegisterDictionaryPref(kUserWallpapers);
registry->RegisterDictionaryPref(kUserWallpapersProperties);
}
void WallpaperManager::AddObservers() {
show_user_name_on_signin_subscription_ =
CrosSettings::Get()->AddSettingsObserver(
kAccountsPrefShowUserNamesOnSignIn,
base::Bind(&WallpaperManager::InitializeRegisteredDeviceWallpaper,
base::Unretained(this)));
}
void WallpaperManager::EnsureLoggedInUserWallpaperLoaded() {
// Some browser tests do not have a shell instance. As no wallpaper is needed
// in these tests anyway, avoid loading one, preventing crashes and speeding
// up the tests.
if (!ash::Shell::HasInstance())
return;
WallpaperInfo info;
if (GetLoggedInUserWallpaperInfo(&info)) {
// TODO(sschmitz): We need an index for default wallpapers for the new UI.
RecordUma(info.type, -1);
if (info == current_user_wallpaper_info_)
return;
}
SetUserWallpaper(UserManager::Get()->GetLoggedInUser()->email());
}
void WallpaperManager::ClearWallpaperCache() {
// Cancel callback for previous cache requests.
weak_factory_.InvalidateWeakPtrs();
wallpaper_cache_.clear();
}
base::FilePath WallpaperManager::GetCustomWallpaperPath(
const char* sub_dir,
const std::string& user_id_hash,
const std::string& file) {
base::FilePath custom_wallpaper_path = GetCustomWallpaperDir(sub_dir);
return custom_wallpaper_path.Append(user_id_hash).Append(file);
}
bool WallpaperManager::GetWallpaperFromCache(const std::string& email,
gfx::ImageSkia* wallpaper) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
CustomWallpaperMap::const_iterator it = wallpaper_cache_.find(email);
if (it != wallpaper_cache_.end()) {
*wallpaper = (*it).second;
return true;
}
return false;
}
base::FilePath WallpaperManager::GetOriginalWallpaperPathForUser(
const std::string& username) {
std::string filename = username + kOriginalCustomWallpaperSuffix;
base::FilePath user_data_dir;
PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
return user_data_dir.AppendASCII(filename);
}
bool WallpaperManager::GetLoggedInUserWallpaperInfo(WallpaperInfo* info) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (UserManager::Get()->IsLoggedInAsStub()) {
info->file = current_user_wallpaper_info_.file = "";
info->layout = current_user_wallpaper_info_.layout =
ash::WALLPAPER_LAYOUT_CENTER_CROPPED;
info->type = current_user_wallpaper_info_.type = User::DEFAULT;
return true;
}
return GetUserWallpaperInfo(UserManager::Get()->GetLoggedInUser()->email(),
info);
}
void WallpaperManager::InitializeWallpaper() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
UserManager* user_manager = UserManager::Get();
CommandLine* command_line = GetComandLine();
if (command_line->HasSwitch(chromeos::switches::kGuestSession)) {
// Guest wallpaper should be initialized when guest login.
// Note: This maybe called before login. So IsLoggedInAsGuest can not be
// used here to determine if current user is guest.
return;
}
if (command_line->HasSwitch(::switches::kTestType))
WizardController::SetZeroDelays();
// Zero delays is also set in autotests.
if (WizardController::IsZeroDelayEnabled()) {
// Ensure tests have some sort of wallpaper.
ash::Shell::GetInstance()->desktop_background_controller()->
CreateEmptyWallpaper();
return;
}
if (!user_manager->IsUserLoggedIn()) {
if (!StartupUtils::IsDeviceRegistered())
SetDefaultWallpaper();
else
InitializeRegisteredDeviceWallpaper();
return;
}
SetUserWallpaper(user_manager->GetLoggedInUser()->email());
}
void WallpaperManager::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
switch (type) {
case chrome::NOTIFICATION_LOGIN_USER_CHANGED: {
ClearWallpaperCache();
BrowserThread::PostDelayedTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&WallpaperManager::MoveLoggedInUserCustomWallpaper,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(kMoveCustomWallpaperDelaySeconds));
break;
}
case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: {
if (!GetComandLine()->HasSwitch(switches::kDisableBootAnimation)) {
BrowserThread::PostDelayedTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&WallpaperManager::CacheUsersWallpapers,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kCacheWallpaperDelayMs));
} else {
should_cache_wallpaper_ = true;
}
break;
}
case chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED: {
NotifyAnimationFinished();
if (should_cache_wallpaper_) {
BrowserThread::PostDelayedTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&WallpaperManager::CacheUsersWallpapers,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kCacheWallpaperDelayMs));
should_cache_wallpaper_ = false;
}
break;
}
default:
NOTREACHED() << "Unexpected notification " << type;
}
}
void WallpaperManager::RemoveUserWallpaperInfo(const std::string& email) {
WallpaperInfo info;
GetUserWallpaperInfo(email, &info);
PrefService* prefs = g_browser_process->local_state();
DictionaryPrefUpdate prefs_wallpapers_info_update(prefs,
prefs::kUsersWallpaperInfo);
prefs_wallpapers_info_update->RemoveWithoutPathExpansion(email, NULL);
DeleteUserWallpapers(email, info.file);
}
bool WallpaperManager::ResizeWallpaper(
const UserImage& wallpaper,
ash::WallpaperLayout layout,
int preferred_width,
int preferred_height,
scoped_refptr<base::RefCountedBytes>* output) {
DCHECK(BrowserThread::GetBlockingPool()->
IsRunningSequenceOnCurrentThread(sequence_token_));
int width = wallpaper.image().width();
int height = wallpaper.image().height();
int resized_width;
int resized_height;
*output = new base::RefCountedBytes();
if (layout == ash::WALLPAPER_LAYOUT_CENTER_CROPPED) {
// Do not resize custom wallpaper if it is smaller than preferred size.
if (!(width > preferred_width && height > preferred_height))
return false;
double horizontal_ratio = static_cast<double>(preferred_width) / width;
double vertical_ratio = static_cast<double>(preferred_height) / height;
if (vertical_ratio > horizontal_ratio) {
resized_width =
RoundPositive(static_cast<double>(width) * vertical_ratio);
resized_height = preferred_height;
} else {
resized_width = preferred_width;
resized_height =
RoundPositive(static_cast<double>(height) * horizontal_ratio);
}
} else if (layout == ash::WALLPAPER_LAYOUT_STRETCH) {
resized_width = preferred_width;
resized_height = preferred_height;
} else {
resized_width = width;
resized_height = height;
}
gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage(
wallpaper.image(),
skia::ImageOperations::RESIZE_LANCZOS3,
gfx::Size(resized_width, resized_height));
SkBitmap image = *(resized_image.bitmap());
SkAutoLockPixels lock_input(image);
gfx::JPEGCodec::Encode(
reinterpret_cast<unsigned char*>(image.getAddr32(0, 0)),
gfx::JPEGCodec::FORMAT_SkBitmap,
image.width(),
image.height(),
image.width() * image.bytesPerPixel(),
kDefaultEncodingQuality, &(*output)->data());
return true;
}
void WallpaperManager::ResizeAndSaveWallpaper(const UserImage& wallpaper,
const base::FilePath& path,
ash::WallpaperLayout layout,
int preferred_width,
int preferred_height) {
if (layout == ash::WALLPAPER_LAYOUT_CENTER) {
// TODO(bshe): Generates cropped custom wallpaper for CENTER layout.
if (base::PathExists(path))
base::DeleteFile(path, false);
return;
}
scoped_refptr<base::RefCountedBytes> data;
if (ResizeWallpaper(wallpaper, layout, preferred_width, preferred_height,
&data)) {
SaveWallpaperInternal(path,
reinterpret_cast<const char*>(data->front()),
data->size());
}
}
void WallpaperManager::SetCustomWallpaper(const std::string& username,
const std::string& user_id_hash,
const std::string& file,
ash::WallpaperLayout layout,
User::WallpaperType type,
const UserImage& wallpaper) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::FilePath wallpaper_path =
GetCustomWallpaperPath(kOriginalWallpaperSubDir, user_id_hash, file);
// If decoded wallpaper is empty, we are probably failed to decode the file.
// Use default wallpaper in this case.
if (wallpaper.image().isNull()) {
SetDefaultWallpaper();
return;
}
bool is_persistent =
!UserManager::Get()->IsUserNonCryptohomeDataEphemeral(username);
wallpaper.image().EnsureRepsForSupportedScales();
scoped_ptr<gfx::ImageSkia> deep_copy(wallpaper.image().DeepCopy());
WallpaperInfo wallpaper_info = {
wallpaper_path.value(),
layout,
type,
// Date field is not used.
base::Time::Now().LocalMidnight()
};
// Block shutdown on this task. Otherwise, we may lost the custom wallpaper
// user selected.
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner =
BrowserThread::GetBlockingPool()->
GetSequencedTaskRunnerWithShutdownBehavior(sequence_token_,
base::SequencedWorkerPool::BLOCK_SHUTDOWN);
// TODO(bshe): This may break if RawImage becomes RefCountedMemory.
blocking_task_runner->PostTask(FROM_HERE,
base::Bind(&WallpaperManager::ProcessCustomWallpaper,
base::Unretained(this),
user_id_hash,
is_persistent,
wallpaper_info,
base::Passed(&deep_copy),
wallpaper.raw_image()));
ash::Shell::GetInstance()->desktop_background_controller()->
SetCustomWallpaper(wallpaper.image(), layout);
std::string relative_path = base::FilePath(user_id_hash).Append(file).value();
// User's custom wallpaper path is determined by relative path and the
// appropriate wallpaper resolution in GetCustomWallpaperInternal.
WallpaperInfo info = {
relative_path,
layout,
User::CUSTOMIZED,
base::Time::Now().LocalMidnight()
};
SetUserWallpaperInfo(username, info, is_persistent);
}
void WallpaperManager::SetDefaultWallpaper() {
current_wallpaper_path_.clear();
if (ash::Shell::GetInstance()->desktop_background_controller()->
SetDefaultWallpaper(UserManager::Get()->IsLoggedInAsGuest()))
loaded_wallpapers_++;
}
void WallpaperManager::SetInitialUserWallpaper(const std::string& username,
bool is_persistent) {
current_user_wallpaper_info_.file = "";
current_user_wallpaper_info_.layout = ash::WALLPAPER_LAYOUT_CENTER_CROPPED;
current_user_wallpaper_info_.type = User::DEFAULT;
current_user_wallpaper_info_.date = base::Time::Now().LocalMidnight();
WallpaperInfo info = current_user_wallpaper_info_;
SetUserWallpaperInfo(username, info, is_persistent);
SetLastSelectedUser(username);
// Some browser tests do not have a shell instance. As no wallpaper is needed
// in these tests anyway, avoid loading one, preventing crashes and speeding
// up the tests.
if (ash::Shell::HasInstance())
SetDefaultWallpaper();
}
void WallpaperManager::SetUserWallpaperInfo(const std::string& username,
const WallpaperInfo& info,
bool is_persistent) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
current_user_wallpaper_info_ = info;
if (!is_persistent)
return;
PrefService* local_state = g_browser_process->local_state();
DictionaryPrefUpdate wallpaper_update(local_state,
prefs::kUsersWallpaperInfo);
base::DictionaryValue* wallpaper_info_dict = new base::DictionaryValue();
wallpaper_info_dict->SetString(kNewWallpaperDateNodeName,
base::Int64ToString(info.date.ToInternalValue()));
wallpaper_info_dict->SetString(kNewWallpaperFileNodeName, info.file);
wallpaper_info_dict->SetInteger(kNewWallpaperLayoutNodeName, info.layout);
wallpaper_info_dict->SetInteger(kNewWallpaperTypeNodeName, info.type);
wallpaper_update->SetWithoutPathExpansion(username, wallpaper_info_dict);
}
void WallpaperManager::SetLastSelectedUser(
const std::string& last_selected_user) {
last_selected_user_ = last_selected_user;
}
void WallpaperManager::SetUserWallpaper(const std::string& email) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (email == UserManager::kGuestUserName) {
SetDefaultWallpaper();
return;
}
if (!UserManager::Get()->IsKnownUser(email))
return;
SetLastSelectedUser(email);
WallpaperInfo info;
if (GetUserWallpaperInfo(email, &info)) {
gfx::ImageSkia user_wallpaper;
current_user_wallpaper_info_ = info;
if (GetWallpaperFromCache(email, &user_wallpaper)) {
ash::Shell::GetInstance()->desktop_background_controller()->
SetCustomWallpaper(user_wallpaper, info.layout);
} else {
if (info.type == User::CUSTOMIZED) {
ash::WallpaperResolution resolution = ash::Shell::GetInstance()->
desktop_background_controller()->GetAppropriateResolution();
const char* sub_dir = (resolution == ash::WALLPAPER_RESOLUTION_SMALL) ?
kSmallWallpaperSubDir : kLargeWallpaperSubDir;
// Wallpaper is not resized when layout is ash::WALLPAPER_LAYOUT_CENTER.
// Original wallpaper should be used in this case.
// TODO(bshe): Generates cropped custom wallpaper for CENTER layout.
if (info.layout == ash::WALLPAPER_LAYOUT_CENTER)
sub_dir = kOriginalWallpaperSubDir;
base::FilePath wallpaper_path = GetCustomWallpaperDir(sub_dir);
wallpaper_path = wallpaper_path.Append(info.file);
if (current_wallpaper_path_ == wallpaper_path)
return;
current_wallpaper_path_ = wallpaper_path;
loaded_wallpapers_++;
task_runner_->PostTask(FROM_HERE,
base::Bind(&WallpaperManager::GetCustomWallpaperInternal,
base::Unretained(this), email, info, wallpaper_path,
true /* update wallpaper */));
return;
}
if (info.file.empty()) {
// Uses default built-in wallpaper when file is empty. Eventually, we
// will only ship one built-in wallpaper in ChromeOS image.
SetDefaultWallpaper();
return;
}
// Load downloaded ONLINE or converted DEFAULT wallpapers.
LoadWallpaper(email, info, true /* update wallpaper */);
}
} else {
SetInitialUserWallpaper(email, true);
}
}
void WallpaperManager::SetWallpaperFromImageSkia(
const gfx::ImageSkia& wallpaper,
ash::WallpaperLayout layout) {
ash::Shell::GetInstance()->desktop_background_controller()->
SetCustomWallpaper(wallpaper, layout);
}
void WallpaperManager::UpdateWallpaper() {
ClearWallpaperCache();
current_wallpaper_path_.clear();
// For GAIA login flow, the last_selected_user_ may not be set before user
// login. If UpdateWallpaper is called at GAIA login screen, no wallpaper will
// be set. It could result a black screen on external monitors.
// See http://crbug.com/265689 for detail.
if (last_selected_user_.empty()) {
SetDefaultWallpaper();
return;
}
SetUserWallpaper(last_selected_user_);
}
void WallpaperManager::AddObserver(WallpaperManager::Observer* observer) {
observers_.AddObserver(observer);
}
void WallpaperManager::RemoveObserver(WallpaperManager::Observer* observer) {
observers_.RemoveObserver(observer);
}
void WallpaperManager::NotifyAnimationFinished() {
FOR_EACH_OBSERVER(
Observer, observers_, OnWallpaperAnimationFinished(last_selected_user_));
}
// WallpaperManager, private: --------------------------------------------------
void WallpaperManager::CacheUsersWallpapers() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
UserList users = UserManager::Get()->GetUsers();
if (!users.empty()) {
UserList::const_iterator it = users.begin();
// Skip the wallpaper of first user in the list. It should have been cached.
it++;
for (int cached = 0;
it != users.end() && cached < kMaxWallpapersToCache;
++it, ++cached) {
std::string user_email = (*it)->email();
CacheUserWallpaper(user_email);
}
}
}
void WallpaperManager::CacheUserWallpaper(const std::string& email) {
if (wallpaper_cache_.find(email) == wallpaper_cache_.end())
return;
WallpaperInfo info;
if (GetUserWallpaperInfo(email, &info)) {
base::FilePath wallpaper_dir;
base::FilePath wallpaper_path;
if (info.type == User::CUSTOMIZED) {
ash::WallpaperResolution resolution = ash::Shell::GetInstance()->
desktop_background_controller()->GetAppropriateResolution();
const char* sub_dir = (resolution == ash::WALLPAPER_RESOLUTION_SMALL) ?
kSmallWallpaperSubDir : kLargeWallpaperSubDir;
base::FilePath wallpaper_path = GetCustomWallpaperDir(sub_dir);
wallpaper_path = wallpaper_path.Append(info.file);
task_runner_->PostTask(FROM_HERE,
base::Bind(&WallpaperManager::GetCustomWallpaperInternal,
base::Unretained(this), email, info, wallpaper_path,
false /* do not update wallpaper */));
return;
}
LoadWallpaper(email, info, false /* do not update wallpaper */);
}
}
void WallpaperManager::ClearObsoleteWallpaperPrefs() {
PrefService* prefs = g_browser_process->local_state();
DictionaryPrefUpdate wallpaper_properties_pref(prefs,
kUserWallpapersProperties);
wallpaper_properties_pref->Clear();
DictionaryPrefUpdate wallpapers_pref(prefs, kUserWallpapers);
wallpapers_pref->Clear();
}
void WallpaperManager::DeleteAllExcept(const base::FilePath& path) {
base::FilePath dir = path.DirName();
if (base::DirectoryExists(dir)) {
base::FileEnumerator files(dir, false, base::FileEnumerator::FILES);
for (base::FilePath current = files.Next(); !current.empty();
current = files.Next()) {
if (current != path)
base::DeleteFile(current, false);
}
}
}
void WallpaperManager::DeleteWallpaperInList(
const std::vector<base::FilePath>& file_list) {
for (std::vector<base::FilePath>::const_iterator it = file_list.begin();
it != file_list.end(); ++it) {
base::FilePath path = *it;
// Some users may still have legacy wallpapers with png extension. We need
// to delete these wallpapers too.
if (!base::DeleteFile(path, true) &&
!base::DeleteFile(path.AddExtension(".png"), false)) {
LOG(ERROR) << "Failed to remove user wallpaper at " << path.value();
}
}
}
void WallpaperManager::DeleteUserWallpapers(const std::string& email,
const std::string& path_to_file) {
std::vector<base::FilePath> file_to_remove;
// Remove small user wallpaper.
base::FilePath wallpaper_path =
GetCustomWallpaperDir(kSmallWallpaperSubDir);
// Remove old directory if exists
file_to_remove.push_back(wallpaper_path.Append(email));
wallpaper_path = wallpaper_path.Append(path_to_file).DirName();
file_to_remove.push_back(wallpaper_path);
// Remove large user wallpaper.
wallpaper_path = GetCustomWallpaperDir(kLargeWallpaperSubDir);
file_to_remove.push_back(wallpaper_path.Append(email));
wallpaper_path = wallpaper_path.Append(path_to_file);
file_to_remove.push_back(wallpaper_path);
// Remove user wallpaper thumbnail.
wallpaper_path = GetCustomWallpaperDir(kThumbnailWallpaperSubDir);
file_to_remove.push_back(wallpaper_path.Append(email));
wallpaper_path = wallpaper_path.Append(path_to_file);
file_to_remove.push_back(wallpaper_path);
// Remove original user wallpaper.
wallpaper_path = GetCustomWallpaperDir(kOriginalWallpaperSubDir);
file_to_remove.push_back(wallpaper_path.Append(email));
wallpaper_path = wallpaper_path.Append(path_to_file);
file_to_remove.push_back(wallpaper_path);
base::WorkerPool::PostTask(
FROM_HERE,
base::Bind(&WallpaperManager::DeleteWallpaperInList,
base::Unretained(this),
file_to_remove),
false);
}
void WallpaperManager::EnsureCustomWallpaperDirectories(
const std::string& user_id_hash) {
base::FilePath dir;
dir = GetCustomWallpaperDir(kSmallWallpaperSubDir);
dir = dir.Append(user_id_hash);
if (!base::PathExists(dir))
base::CreateDirectory(dir);
dir = GetCustomWallpaperDir(kLargeWallpaperSubDir);
dir = dir.Append(user_id_hash);
if (!base::PathExists(dir))
base::CreateDirectory(dir);
dir = GetCustomWallpaperDir(kOriginalWallpaperSubDir);
dir = dir.Append(user_id_hash);
if (!base::PathExists(dir))
base::CreateDirectory(dir);
dir = GetCustomWallpaperDir(kThumbnailWallpaperSubDir);
dir = dir.Append(user_id_hash);
if (!base::PathExists(dir))
base::CreateDirectory(dir);
}
CommandLine* WallpaperManager::GetComandLine() {
CommandLine* command_line = command_line_for_testing_ ?
command_line_for_testing_ : CommandLine::ForCurrentProcess();
return command_line;
}
void WallpaperManager::InitializeRegisteredDeviceWallpaper() {
if (UserManager::Get()->IsUserLoggedIn())
return;
bool disable_boot_animation = GetComandLine()->
HasSwitch(switches::kDisableBootAnimation);
bool show_users = true;
bool result = CrosSettings::Get()->GetBoolean(
kAccountsPrefShowUserNamesOnSignIn, &show_users);
DCHECK(result) << "Unable to fetch setting "
<< kAccountsPrefShowUserNamesOnSignIn;
const chromeos::UserList& users = UserManager::Get()->GetUsers();
if (!show_users || users.empty()) {
// Boot into sign in form, preload default wallpaper.
SetDefaultWallpaper();
return;
}
if (!disable_boot_animation) {
// Normal boot, load user wallpaper.
// If normal boot animation is disabled wallpaper would be set
// asynchronously once user pods are loaded.
SetUserWallpaper(users[0]->email());
}
}
void WallpaperManager::LoadWallpaper(const std::string& email,
const WallpaperInfo& info,
bool update_wallpaper) {
base::FilePath wallpaper_dir;
base::FilePath wallpaper_path;
if (info.type == User::ONLINE) {
std::string file_name = GURL(info.file).ExtractFileName();
ash::WallpaperResolution resolution = ash::Shell::GetInstance()->
desktop_background_controller()->GetAppropriateResolution();
// Only solid color wallpapers have stretch layout and they have only one
// resolution.
if (info.layout != ash::WALLPAPER_LAYOUT_STRETCH &&
resolution == ash::WALLPAPER_RESOLUTION_SMALL) {
file_name = base::FilePath(file_name).InsertBeforeExtension(
kSmallWallpaperSuffix).value();
}
CHECK(PathService::Get(chrome::DIR_CHROMEOS_WALLPAPERS, &wallpaper_dir));
wallpaper_path = wallpaper_dir.Append(file_name);
if (current_wallpaper_path_ == wallpaper_path)
return;
if (update_wallpaper)
current_wallpaper_path_ = wallpaper_path;
loaded_wallpapers_++;
StartLoad(email, info, update_wallpaper, wallpaper_path);
} else if (info.type == User::DEFAULT) {
// Default wallpapers are migrated from M21 user profiles. A code refactor
// overlooked that case and caused these wallpapers not being loaded at all.
// On some slow devices, it caused login webui not visible after upgrade to
// M26 from M21. See crosbug.com/38429 for details.
base::FilePath user_data_dir;
PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
wallpaper_path = user_data_dir.Append(info.file);
StartLoad(email, info, update_wallpaper, wallpaper_path);
} else {
// In unexpected cases, revert to default wallpaper to fail safely. See
// crosbug.com/38429.
LOG(ERROR) << "Wallpaper reverts to default unexpected.";
SetDefaultWallpaper();
}
}
bool WallpaperManager::GetUserWallpaperInfo(const std::string& email,
WallpaperInfo* info){
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (UserManager::Get()->IsUserNonCryptohomeDataEphemeral(email)) {
// Default to the values cached in memory.
*info = current_user_wallpaper_info_;
// Ephemeral users do not save anything to local state. But we have got
// wallpaper info from memory. Returns true.
return true;
}
const DictionaryValue* user_wallpapers = g_browser_process->local_state()->
GetDictionary(prefs::kUsersWallpaperInfo);
const base::DictionaryValue* wallpaper_info_dict;
if (user_wallpapers->GetDictionaryWithoutPathExpansion(
email, &wallpaper_info_dict)) {
info->file = "";
info->layout = ash::WALLPAPER_LAYOUT_CENTER_CROPPED;
info->type = User::UNKNOWN;
info->date = base::Time::Now().LocalMidnight();
wallpaper_info_dict->GetString(kNewWallpaperFileNodeName, &(info->file));
int temp;
wallpaper_info_dict->GetInteger(kNewWallpaperLayoutNodeName, &temp);
info->layout = static_cast<ash::WallpaperLayout>(temp);
wallpaper_info_dict->GetInteger(kNewWallpaperTypeNodeName, &temp);
info->type = static_cast<User::WallpaperType>(temp);
std::string date_string;
int64 val;
if (!(wallpaper_info_dict->GetString(kNewWallpaperDateNodeName,
&date_string) &&
base::StringToInt64(date_string, &val)))
val = 0;
info->date = base::Time::FromInternalValue(val);
return true;
}
return false;
}
void WallpaperManager::MoveCustomWallpapersOnWorker(
const std::string& email,
const std::string& user_id_hash) {
DCHECK(BrowserThread::GetBlockingPool()->
IsRunningSequenceOnCurrentThread(sequence_token_));
if (MoveCustomWallpaperDirectory(kOriginalWallpaperSubDir,
email,
user_id_hash)) {
// Consider success if the original wallpaper is moved to the new directory.
// Original wallpaper is the fallback if the correct resolution wallpaper
// can not be found.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&WallpaperManager::MoveCustomWallpapersSuccess,
base::Unretained(this),
email,
user_id_hash));
}
MoveCustomWallpaperDirectory(kLargeWallpaperSubDir, email, user_id_hash);
MoveCustomWallpaperDirectory(kSmallWallpaperSubDir, email, user_id_hash);
MoveCustomWallpaperDirectory(kThumbnailWallpaperSubDir, email, user_id_hash);
}
void WallpaperManager::MoveCustomWallpapersSuccess(
const std::string& email,
const std::string& user_id_hash) {
WallpaperInfo info;
GetUserWallpaperInfo(email, &info);
if (info.type == User::CUSTOMIZED) {
// New file field should include user id hash in addition to file name.
// This is needed because at login screen, user id hash is not available.
std::string relative_path =
base::FilePath(user_id_hash).Append(info.file).value();
info.file = relative_path;
bool is_persistent =
!UserManager::Get()->IsUserNonCryptohomeDataEphemeral(email);
SetUserWallpaperInfo(email, info, is_persistent);
}
}
void WallpaperManager::MoveLoggedInUserCustomWallpaper() {
const User* logged_in_user = UserManager::Get()->GetLoggedInUser();
task_runner_->PostTask(
FROM_HERE,
base::Bind(&WallpaperManager::MoveCustomWallpapersOnWorker,
base::Unretained(this),
logged_in_user->email(),
logged_in_user->username_hash()));
}
void WallpaperManager::GetCustomWallpaperInternal(
const std::string& email,
const WallpaperInfo& info,
const base::FilePath& wallpaper_path,
bool update_wallpaper) {
DCHECK(BrowserThread::GetBlockingPool()->
IsRunningSequenceOnCurrentThread(sequence_token_));
base::FilePath valid_path = wallpaper_path;
if (!base::PathExists(wallpaper_path)) {
// Falls back on original file if the correct resoltuion file does not
// exist. This may happen when the original custom wallpaper is small or
// browser shutdown before resized wallpaper saved.
valid_path = GetCustomWallpaperDir(kOriginalWallpaperSubDir);
valid_path = valid_path.Append(info.file);
}
if (!base::PathExists(valid_path)) {
// Falls back to custom wallpaper that uses email as part of its file path.
// Note that email is used instead of user_id_hash here.
valid_path = GetCustomWallpaperPath(kOriginalWallpaperSubDir, email,
info.file);
}
if (!base::PathExists(valid_path)) {
LOG(ERROR) << "Failed to load previously selected custom wallpaper. " <<
"Fallback to default wallpaper";
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&WallpaperManager::SetDefaultWallpaper,
base::Unretained(this)));
} else {
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&WallpaperManager::StartLoad,
base::Unretained(this),
email,
info,
update_wallpaper,
valid_path));
}
}
void WallpaperManager::OnWallpaperDecoded(const std::string& email,
ash::WallpaperLayout layout,
bool update_wallpaper,
const UserImage& wallpaper) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TRACE_EVENT_ASYNC_END0("ui", "LoadAndDecodeWallpaper", this);
// If decoded wallpaper is empty, we are probably failed to decode the file.
// Use default wallpaper in this case.
if (wallpaper.image().isNull()) {
// Updates user pref to default wallpaper.
WallpaperInfo info = {
"",
ash::WALLPAPER_LAYOUT_CENTER_CROPPED,
User::DEFAULT,
base::Time::Now().LocalMidnight()
};
SetUserWallpaperInfo(email, info, true);
if (update_wallpaper)
SetDefaultWallpaper();
return;
}
// Only cache user wallpaper at login screen.
if (!UserManager::Get()->IsUserLoggedIn()) {
wallpaper_cache_.insert(std::make_pair(email, wallpaper.image()));
}
if (update_wallpaper) {
ash::Shell::GetInstance()->desktop_background_controller()->
SetCustomWallpaper(wallpaper.image(), layout);
}
}
void WallpaperManager::ProcessCustomWallpaper(
const std::string& user_id_hash,
bool persistent,
const WallpaperInfo& info,
scoped_ptr<gfx::ImageSkia> image,
const UserImage::RawImage& raw_image) {
DCHECK(BrowserThread::GetBlockingPool()->
IsRunningSequenceOnCurrentThread(sequence_token_));
UserImage wallpaper(*image.get(), raw_image);
if (persistent) {
SaveCustomWallpaper(user_id_hash, base::FilePath(info.file), info.layout,
wallpaper);
}
}
void WallpaperManager::SaveCustomWallpaper(const std::string& user_id_hash,
const base::FilePath& original_path,
ash::WallpaperLayout layout,
const UserImage& wallpaper) {
DCHECK(BrowserThread::GetBlockingPool()->
IsRunningSequenceOnCurrentThread(sequence_token_));
EnsureCustomWallpaperDirectories(user_id_hash);
std::string file_name = original_path.BaseName().value();
base::FilePath small_wallpaper_path =
GetCustomWallpaperPath(kSmallWallpaperSubDir, user_id_hash, file_name);
base::FilePath large_wallpaper_path =
GetCustomWallpaperPath(kLargeWallpaperSubDir, user_id_hash, file_name);
// Re-encode orginal file to jpeg format and saves the result in case that
// resized wallpaper is not generated (i.e. chrome shutdown before resized
// wallpaper is saved).
ResizeAndSaveWallpaper(wallpaper, original_path,
ash::WALLPAPER_LAYOUT_STRETCH,
wallpaper.image().width(),
wallpaper.image().height());
DeleteAllExcept(original_path);
ResizeAndSaveWallpaper(wallpaper, small_wallpaper_path, layout,
ash::kSmallWallpaperMaxWidth,
ash::kSmallWallpaperMaxHeight);
DeleteAllExcept(small_wallpaper_path);
ResizeAndSaveWallpaper(wallpaper, large_wallpaper_path, layout,
ash::kLargeWallpaperMaxWidth,
ash::kLargeWallpaperMaxHeight);
DeleteAllExcept(large_wallpaper_path);
}
void WallpaperManager::RecordUma(User::WallpaperType type, int index) {
UMA_HISTOGRAM_ENUMERATION("Ash.Wallpaper.Type", type,
User::WALLPAPER_TYPE_COUNT);
}
void WallpaperManager::SaveWallpaperInternal(const base::FilePath& path,
const char* data,
int size) {
int written_bytes = file_util::WriteFile(path, data, size);
DCHECK(written_bytes == size);
}
void WallpaperManager::StartLoad(const std::string& email,
const WallpaperInfo& info,
bool update_wallpaper,
const base::FilePath& wallpaper_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TRACE_EVENT_ASYNC_BEGIN0("ui", "LoadAndDecodeWallpaper", this);
wallpaper_loader_->Start(wallpaper_path.value(), 0,
base::Bind(&WallpaperManager::OnWallpaperDecoded,
base::Unretained(this),
email,
info.layout,
update_wallpaper));
}
} // namespace chromeos