普通文本  |  464行  |  16.05 KB

// 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;

}  // 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_->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::Window* root_window) {
  // The background hasn't been set yet.
  if (desktop_background_mode_ == BACKGROUND_NONE)
    return;

  // Handle resolution change for "built-in" images.
  gfx::Size max_display_size = GetMaxDisplaySizeInNative();
  if (current_max_display_size_ != max_display_size) {
    current_max_display_size_ = max_display_size;
    if (desktop_background_mode_ == BACKGROUND_IMAGE &&
        current_wallpaper_.get())
      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 {
    switch_name = use_large ? switches::kAshDefaultWallpaperLarge :
        switches::kAshDefaultWallpaperSmall;
  }
  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;
    if (desktop_background_mode_ == BACKGROUND_IMAGE &&
        current_wallpaper_.get()) {
      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() &&
      (WallpaperResizer::GetImageId(image) ==
       current_wallpaper_->original_image_id());
}

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::Window* 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() {
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  for (aura::Window::Windows::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