// 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; internal::RootWindowLayoutManager* GetRootWindowLayoutManager( aura::RootWindow* root_window) { return static_cast( 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 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 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(); } scoped_ptr bitmap(gfx::JPEGCodec::Decode( reinterpret_cast(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 file_bitmap_; scoped_ptr 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 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 displays = Shell::GetScreen()->GetAllDisplays(); internal::DisplayManager* display_manager = Shell::GetInstance()->display_manager(); for (std::vector::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