blob: 180f0089724577429f3dce90bcf17e606d428aa8 [file] [log] [blame]
// Copyright (c) 2012 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 "ash/desktop_background/desktop_background_controller.h"
#include "ash/ash_switches.h"
#include "ash/desktop_background/desktop_background_controller_observer.h"
#include "ash/desktop_background/desktop_background_view.h"
#include "ash/desktop_background/desktop_background_widget_controller.h"
#include "ash/desktop_background/user_wallpaper_delegate.h"
#include "ash/desktop_background/wallpaper_resizer.h"
#include "ash/display/display_info.h"
#include "ash/display/display_manager.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/shell_factory.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/root_window_layout_manager.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/synchronization/cancellation_flag.h"
#include "base/threading/worker_pool.h"
#include "content/public/browser/browser_thread.h"
#include "grit/ash_resources.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/rect.h"
#include "ui/views/widget/widget.h"
using ash::internal::DesktopBackgroundWidgetController;
using content::BrowserThread;
namespace ash {
namespace {
// How long to wait reloading the wallpaper after the max display has
// changed?
const int kWallpaperReloadDelayMs = 2000;
const SkColor kTransparentColor = SkColorSetARGB(0x00, 0x00, 0x00, 0x00);
internal::RootWindowLayoutManager* GetRootWindowLayoutManager(
aura::RootWindow* root_window) {
return static_cast<internal::RootWindowLayoutManager*>(
root_window->layout_manager());
}
} // namespace
const int kSmallWallpaperMaxWidth = 1366;
const int kSmallWallpaperMaxHeight = 800;
const int kLargeWallpaperMaxWidth = 2560;
const int kLargeWallpaperMaxHeight = 1700;
const int kWallpaperThumbnailWidth = 108;
const int kWallpaperThumbnailHeight = 68;
// DesktopBackgroundController::WallpaperLoader wraps background wallpaper
// loading.
class DesktopBackgroundController::WallpaperLoader
: public base::RefCountedThreadSafe<
DesktopBackgroundController::WallpaperLoader> {
public:
// If set, |file_path| must be a trusted (i.e. read-only,
// non-user-controlled) file containing a JPEG image.
WallpaperLoader(const base::FilePath& file_path,
WallpaperLayout file_layout,
int resource_id,
WallpaperLayout resource_layout)
: file_path_(file_path),
file_layout_(file_layout),
resource_id_(resource_id),
resource_layout_(resource_layout) {
}
static void LoadOnWorkerPoolThread(scoped_refptr<WallpaperLoader> loader) {
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
loader->LoadWallpaper();
}
const base::FilePath& file_path() const { return file_path_; }
int resource_id() const { return resource_id_; }
void Cancel() {
cancel_flag_.Set();
}
WallpaperResizer* ReleaseWallpaperResizer() {
return wallpaper_resizer_.release();
}
private:
friend class base::RefCountedThreadSafe<
DesktopBackgroundController::WallpaperLoader>;
// Loads a JPEG image from |path|, a trusted file -- note that the image
// is not loaded in a sandboxed process. Returns an empty pointer on
// error.
static scoped_ptr<SkBitmap> LoadSkBitmapFromJPEGFile(
const base::FilePath& path) {
std::string data;
if (!base::ReadFileToString(path, &data)) {
LOG(ERROR) << "Unable to read data from " << path.value();
return scoped_ptr<SkBitmap>();
}
scoped_ptr<SkBitmap> bitmap(gfx::JPEGCodec::Decode(
reinterpret_cast<const unsigned char*>(data.data()), data.size()));
if (!bitmap)
LOG(ERROR) << "Unable to decode JPEG data from " << path.value();
return bitmap.Pass();
}
void LoadWallpaper() {
if (cancel_flag_.IsSet())
return;
if (!file_path_.empty())
file_bitmap_ = LoadSkBitmapFromJPEGFile(file_path_);
if (cancel_flag_.IsSet())
return;
if (file_bitmap_) {
gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(*file_bitmap_);
wallpaper_resizer_.reset(new WallpaperResizer(
image, GetMaxDisplaySizeInNative(), file_layout_));
} else {
wallpaper_resizer_.reset(new WallpaperResizer(
resource_id_, GetMaxDisplaySizeInNative(), resource_layout_));
}
}
~WallpaperLoader() {}
base::CancellationFlag cancel_flag_;
// Bitmap loaded from |file_path_|.
scoped_ptr<SkBitmap> file_bitmap_;
scoped_ptr<WallpaperResizer> wallpaper_resizer_;
// Path to a trusted JPEG file.
base::FilePath file_path_;
// Layout to be used when displaying the image from |file_path_|.
WallpaperLayout file_layout_;
// ID of an image resource to use if |file_path_| is empty or unloadable.
int resource_id_;
// Layout to be used when displaying |resource_id_|.
WallpaperLayout resource_layout_;
DISALLOW_COPY_AND_ASSIGN(WallpaperLoader);
};
DesktopBackgroundController::DesktopBackgroundController()
: command_line_for_testing_(NULL),
locked_(false),
desktop_background_mode_(BACKGROUND_NONE),
current_default_wallpaper_resource_id_(-1),
weak_ptr_factory_(this),
wallpaper_reload_delay_(kWallpaperReloadDelayMs) {
Shell::GetInstance()->display_controller()->AddObserver(this);
}
DesktopBackgroundController::~DesktopBackgroundController() {
CancelPendingWallpaperOperation();
Shell::GetInstance()->display_controller()->RemoveObserver(this);
}
gfx::ImageSkia DesktopBackgroundController::GetWallpaper() const {
if (current_wallpaper_)
return current_wallpaper_->wallpaper_image();
return gfx::ImageSkia();
}
void DesktopBackgroundController::AddObserver(
DesktopBackgroundControllerObserver* observer) {
observers_.AddObserver(observer);
}
void DesktopBackgroundController::RemoveObserver(
DesktopBackgroundControllerObserver* observer) {
observers_.RemoveObserver(observer);
}
WallpaperLayout DesktopBackgroundController::GetWallpaperLayout() const {
if (current_wallpaper_)
return current_wallpaper_->layout();
return WALLPAPER_LAYOUT_CENTER_CROPPED;
}
void DesktopBackgroundController::OnRootWindowAdded(
aura::RootWindow* root_window) {
// The background hasn't been set yet.
if (desktop_background_mode_ == BACKGROUND_NONE)
return;
gfx::Size max_display_size = GetMaxDisplaySizeInNative();
// Handle resolution change for "built-in" images.
if (BACKGROUND_IMAGE == desktop_background_mode_ &&
current_wallpaper_.get() &&
current_max_display_size_ != max_display_size) {
current_max_display_size_ = max_display_size;
UpdateWallpaper();
}
InstallDesktopController(root_window);
}
bool DesktopBackgroundController::SetDefaultWallpaper(bool is_guest) {
const bool use_large =
GetAppropriateResolution() == WALLPAPER_RESOLUTION_LARGE;
base::FilePath file_path;
WallpaperLayout file_layout = use_large ? WALLPAPER_LAYOUT_CENTER_CROPPED :
WALLPAPER_LAYOUT_CENTER;
int resource_id = use_large ? IDR_AURA_WALLPAPER_DEFAULT_LARGE :
IDR_AURA_WALLPAPER_DEFAULT_SMALL;
WallpaperLayout resource_layout = WALLPAPER_LAYOUT_TILE;
CommandLine* command_line = command_line_for_testing_ ?
command_line_for_testing_ : CommandLine::ForCurrentProcess();
const char* switch_name = NULL;
if (is_guest) {
switch_name = use_large ? switches::kAshGuestWallpaperLarge :
switches::kAshGuestWallpaperSmall;
} else {
const char* oem_switch_name = use_large ? switches::kAshOemWallpaperLarge :
switches::kAshOemWallpaperSmall;
const char* default_switch_name = use_large ?
switches::kAshDefaultWallpaperLarge :
switches::kAshDefaultWallpaperSmall;
switch_name = command_line->HasSwitch(oem_switch_name) ? oem_switch_name :
default_switch_name;
}
file_path = command_line->GetSwitchValuePath(switch_name);
if (DefaultWallpaperIsAlreadyLoadingOrLoaded(file_path, resource_id))
return false;
CancelPendingWallpaperOperation();
wallpaper_loader_ = new WallpaperLoader(
file_path, file_layout, resource_id, resource_layout);
base::WorkerPool::PostTaskAndReply(
FROM_HERE,
base::Bind(&WallpaperLoader::LoadOnWorkerPoolThread, wallpaper_loader_),
base::Bind(&DesktopBackgroundController::OnDefaultWallpaperLoadCompleted,
weak_ptr_factory_.GetWeakPtr(),
wallpaper_loader_),
true /* task_is_slow */);
return true;
}
void DesktopBackgroundController::SetCustomWallpaper(
const gfx::ImageSkia& image,
WallpaperLayout layout) {
CancelPendingWallpaperOperation();
if (CustomWallpaperIsAlreadyLoaded(image))
return;
current_wallpaper_.reset(new WallpaperResizer(
image, GetMaxDisplaySizeInNative(), layout));
current_wallpaper_->StartResize();
current_default_wallpaper_path_ = base::FilePath();
current_default_wallpaper_resource_id_ = -1;
FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver, observers_,
OnWallpaperDataChanged());
SetDesktopBackgroundImageMode();
}
void DesktopBackgroundController::CancelPendingWallpaperOperation() {
// Set canceled flag of previous request to skip unneeded loading.
if (wallpaper_loader_.get())
wallpaper_loader_->Cancel();
// Cancel reply callback for previous request.
weak_ptr_factory_.InvalidateWeakPtrs();
}
void DesktopBackgroundController::CreateEmptyWallpaper() {
current_wallpaper_.reset(NULL);
SetDesktopBackgroundImageMode();
}
WallpaperResolution DesktopBackgroundController::GetAppropriateResolution() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
gfx::Size size = GetMaxDisplaySizeInNative();
return (size.width() > kSmallWallpaperMaxWidth ||
size.height() > kSmallWallpaperMaxHeight) ?
WALLPAPER_RESOLUTION_LARGE : WALLPAPER_RESOLUTION_SMALL;
}
bool DesktopBackgroundController::MoveDesktopToLockedContainer() {
if (locked_)
return false;
locked_ = true;
return ReparentBackgroundWidgets(GetBackgroundContainerId(false),
GetBackgroundContainerId(true));
}
bool DesktopBackgroundController::MoveDesktopToUnlockedContainer() {
if (!locked_)
return false;
locked_ = false;
return ReparentBackgroundWidgets(GetBackgroundContainerId(true),
GetBackgroundContainerId(false));
}
void DesktopBackgroundController::OnDisplayConfigurationChanged() {
gfx::Size max_display_size = GetMaxDisplaySizeInNative();
if (current_max_display_size_ != max_display_size) {
current_max_display_size_ = max_display_size;
timer_.Stop();
timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(wallpaper_reload_delay_),
this,
&DesktopBackgroundController::UpdateWallpaper);
}
}
bool DesktopBackgroundController::DefaultWallpaperIsAlreadyLoadingOrLoaded(
const base::FilePath& image_file, int image_resource_id) const {
return (wallpaper_loader_.get() &&
wallpaper_loader_->file_path() == image_file &&
wallpaper_loader_->resource_id() == image_resource_id) ||
(current_wallpaper_.get() &&
current_default_wallpaper_path_ == image_file &&
current_default_wallpaper_resource_id_ == image_resource_id);
}
bool DesktopBackgroundController::CustomWallpaperIsAlreadyLoaded(
const gfx::ImageSkia& image) const {
return current_wallpaper_.get() &&
current_wallpaper_->wallpaper_image().BackedBySameObjectAs(image);
}
void DesktopBackgroundController::SetDesktopBackgroundImageMode() {
desktop_background_mode_ = BACKGROUND_IMAGE;
InstallDesktopControllerForAllWindows();
}
void DesktopBackgroundController::OnDefaultWallpaperLoadCompleted(
scoped_refptr<WallpaperLoader> loader) {
current_wallpaper_.reset(loader->ReleaseWallpaperResizer());
current_wallpaper_->StartResize();
current_default_wallpaper_path_ = loader->file_path();
current_default_wallpaper_resource_id_ = loader->resource_id();
FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver, observers_,
OnWallpaperDataChanged());
SetDesktopBackgroundImageMode();
DCHECK(loader.get() == wallpaper_loader_.get());
wallpaper_loader_ = NULL;
}
void DesktopBackgroundController::InstallDesktopController(
aura::RootWindow* root_window) {
internal::DesktopBackgroundWidgetController* component = NULL;
int container_id = GetBackgroundContainerId(locked_);
switch (desktop_background_mode_) {
case BACKGROUND_IMAGE: {
views::Widget* widget = internal::CreateDesktopBackground(root_window,
container_id);
component = new internal::DesktopBackgroundWidgetController(widget);
break;
}
case BACKGROUND_NONE:
NOTREACHED();
return;
}
internal::GetRootWindowController(root_window)->
SetAnimatingWallpaperController(
new internal::AnimatingDesktopController(component));
component->StartAnimating(internal::GetRootWindowController(root_window));
}
void DesktopBackgroundController::InstallDesktopControllerForAllWindows() {
Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
for (Shell::RootWindowList::iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
InstallDesktopController(*iter);
}
current_max_display_size_ = GetMaxDisplaySizeInNative();
}
bool DesktopBackgroundController::ReparentBackgroundWidgets(int src_container,
int dst_container) {
bool moved = false;
Shell::RootWindowControllerList controllers =
Shell::GetAllRootWindowControllers();
for (Shell::RootWindowControllerList::iterator iter = controllers.begin();
iter != controllers.end(); ++iter) {
internal::RootWindowController* root_window_controller = *iter;
// In the steady state (no animation playing) the background widget
// controller exists in the RootWindowController.
DesktopBackgroundWidgetController* desktop_controller =
root_window_controller->wallpaper_controller();
if (desktop_controller) {
moved |= desktop_controller->Reparent(
root_window_controller->root_window(),
src_container,
dst_container);
}
// During desktop show animations the controller lives in
// AnimatingDesktopController owned by RootWindowController.
// NOTE: If a wallpaper load happens during a desktop show animation there
// can temporarily be two desktop background widgets. We must reparent
// both of them - one above and one here.
DesktopBackgroundWidgetController* animating_controller =
root_window_controller->animating_wallpaper_controller() ?
root_window_controller->animating_wallpaper_controller()->
GetController(false) :
NULL;
if (animating_controller) {
moved |= animating_controller->Reparent(
root_window_controller->root_window(),
src_container,
dst_container);
}
}
return moved;
}
int DesktopBackgroundController::GetBackgroundContainerId(bool locked) {
return locked ? internal::kShellWindowId_LockScreenBackgroundContainer :
internal::kShellWindowId_DesktopBackgroundContainer;
}
void DesktopBackgroundController::UpdateWallpaper() {
current_wallpaper_.reset(NULL);
current_default_wallpaper_path_ = base::FilePath();
current_default_wallpaper_resource_id_ = -1;
ash::Shell::GetInstance()->user_wallpaper_delegate()->
UpdateWallpaper();
}
// static
gfx::Size DesktopBackgroundController::GetMaxDisplaySizeInNative() {
int width = 0;
int height = 0;
std::vector<gfx::Display> displays = Shell::GetScreen()->GetAllDisplays();
internal::DisplayManager* display_manager =
Shell::GetInstance()->display_manager();
for (std::vector<gfx::Display>::iterator iter = displays.begin();
iter != displays.end(); ++iter) {
// Don't use size_in_pixel because we want to use the native pixel size.
gfx::Size size_in_pixel =
display_manager->GetDisplayInfo(iter->id()).bounds_in_native().size();
if (iter->rotation() == gfx::Display::ROTATE_90 ||
iter->rotation() == gfx::Display::ROTATE_270) {
size_in_pixel = gfx::Size(size_in_pixel.height(), size_in_pixel.width());
}
width = std::max(size_in_pixel.width(), width);
height = std::max(size_in_pixel.height(), height);
}
return gfx::Size(width, height);
}
} // namespace ash