1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ash/desktop_background/desktop_background_controller.h"
6
7#include "ash/ash_switches.h"
8#include "ash/desktop_background/desktop_background_controller_observer.h"
9#include "ash/desktop_background/desktop_background_view.h"
10#include "ash/desktop_background/desktop_background_widget_controller.h"
11#include "ash/desktop_background/user_wallpaper_delegate.h"
12#include "ash/desktop_background/wallpaper_resizer.h"
13#include "ash/display/display_info.h"
14#include "ash/display/display_manager.h"
15#include "ash/root_window_controller.h"
16#include "ash/shell.h"
17#include "ash/shell_factory.h"
18#include "ash/shell_window_ids.h"
19#include "ash/wm/root_window_layout_manager.h"
20#include "base/bind.h"
21#include "base/command_line.h"
22#include "base/file_util.h"
23#include "base/logging.h"
24#include "base/synchronization/cancellation_flag.h"
25#include "base/threading/worker_pool.h"
26#include "content/public/browser/browser_thread.h"
27#include "grit/ash_resources.h"
28#include "ui/aura/root_window.h"
29#include "ui/aura/window.h"
30#include "ui/compositor/layer.h"
31#include "ui/gfx/codec/jpeg_codec.h"
32#include "ui/gfx/image/image_skia.h"
33#include "ui/gfx/rect.h"
34#include "ui/views/widget/widget.h"
35
36using ash::internal::DesktopBackgroundWidgetController;
37using content::BrowserThread;
38
39namespace ash {
40namespace {
41
42// How long to wait reloading the wallpaper after the max display has
43// changed?
44const int kWallpaperReloadDelayMs = 2000;
45
46}  // namespace
47
48const int kSmallWallpaperMaxWidth = 1366;
49const int kSmallWallpaperMaxHeight = 800;
50const int kLargeWallpaperMaxWidth = 2560;
51const int kLargeWallpaperMaxHeight = 1700;
52const int kWallpaperThumbnailWidth = 108;
53const int kWallpaperThumbnailHeight = 68;
54
55// DesktopBackgroundController::WallpaperLoader wraps background wallpaper
56// loading.
57class DesktopBackgroundController::WallpaperLoader
58    : public base::RefCountedThreadSafe<
59          DesktopBackgroundController::WallpaperLoader> {
60 public:
61  // If set, |file_path| must be a trusted (i.e. read-only,
62  // non-user-controlled) file containing a JPEG image.
63  WallpaperLoader(const base::FilePath& file_path,
64                  WallpaperLayout file_layout,
65                  int resource_id,
66                  WallpaperLayout resource_layout)
67      : file_path_(file_path),
68        file_layout_(file_layout),
69        resource_id_(resource_id),
70        resource_layout_(resource_layout) {
71  }
72
73  static void LoadOnWorkerPoolThread(scoped_refptr<WallpaperLoader> loader) {
74    DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
75    loader->LoadWallpaper();
76  }
77
78  const base::FilePath& file_path() const { return file_path_; }
79  int resource_id() const { return resource_id_; }
80
81  void Cancel() {
82    cancel_flag_.Set();
83  }
84
85  WallpaperResizer* ReleaseWallpaperResizer() {
86    return wallpaper_resizer_.release();
87  }
88
89 private:
90  friend class base::RefCountedThreadSafe<
91      DesktopBackgroundController::WallpaperLoader>;
92
93  // Loads a JPEG image from |path|, a trusted file -- note that the image
94  // is not loaded in a sandboxed process. Returns an empty pointer on
95  // error.
96  static scoped_ptr<SkBitmap> LoadSkBitmapFromJPEGFile(
97      const base::FilePath& path) {
98    std::string data;
99    if (!base::ReadFileToString(path, &data)) {
100      LOG(ERROR) << "Unable to read data from " << path.value();
101      return scoped_ptr<SkBitmap>();
102    }
103
104    scoped_ptr<SkBitmap> bitmap(gfx::JPEGCodec::Decode(
105        reinterpret_cast<const unsigned char*>(data.data()), data.size()));
106    if (!bitmap)
107      LOG(ERROR) << "Unable to decode JPEG data from " << path.value();
108    return bitmap.Pass();
109  }
110
111  void LoadWallpaper() {
112    if (cancel_flag_.IsSet())
113      return;
114
115    if (!file_path_.empty())
116      file_bitmap_ = LoadSkBitmapFromJPEGFile(file_path_);
117
118    if (cancel_flag_.IsSet())
119      return;
120
121    if (file_bitmap_) {
122      gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(*file_bitmap_);
123      wallpaper_resizer_.reset(new WallpaperResizer(
124          image, GetMaxDisplaySizeInNative(), file_layout_));
125    } else {
126      wallpaper_resizer_.reset(new WallpaperResizer(
127          resource_id_, GetMaxDisplaySizeInNative(), resource_layout_));
128    }
129  }
130
131  ~WallpaperLoader() {}
132
133  base::CancellationFlag cancel_flag_;
134
135  // Bitmap loaded from |file_path_|.
136  scoped_ptr<SkBitmap> file_bitmap_;
137
138  scoped_ptr<WallpaperResizer> wallpaper_resizer_;
139
140  // Path to a trusted JPEG file.
141  base::FilePath file_path_;
142
143  // Layout to be used when displaying the image from |file_path_|.
144  WallpaperLayout file_layout_;
145
146  // ID of an image resource to use if |file_path_| is empty or unloadable.
147  int resource_id_;
148
149  // Layout to be used when displaying |resource_id_|.
150  WallpaperLayout resource_layout_;
151
152  DISALLOW_COPY_AND_ASSIGN(WallpaperLoader);
153};
154
155DesktopBackgroundController::DesktopBackgroundController()
156    : command_line_for_testing_(NULL),
157      locked_(false),
158      desktop_background_mode_(BACKGROUND_NONE),
159      current_default_wallpaper_resource_id_(-1),
160      weak_ptr_factory_(this),
161      wallpaper_reload_delay_(kWallpaperReloadDelayMs) {
162  Shell::GetInstance()->display_controller()->AddObserver(this);
163}
164
165DesktopBackgroundController::~DesktopBackgroundController() {
166  CancelPendingWallpaperOperation();
167  Shell::GetInstance()->display_controller()->RemoveObserver(this);
168}
169
170gfx::ImageSkia DesktopBackgroundController::GetWallpaper() const {
171  if (current_wallpaper_)
172    return current_wallpaper_->image();
173  return gfx::ImageSkia();
174}
175
176void DesktopBackgroundController::AddObserver(
177    DesktopBackgroundControllerObserver* observer) {
178  observers_.AddObserver(observer);
179}
180
181void DesktopBackgroundController::RemoveObserver(
182    DesktopBackgroundControllerObserver* observer) {
183  observers_.RemoveObserver(observer);
184}
185
186WallpaperLayout DesktopBackgroundController::GetWallpaperLayout() const {
187  if (current_wallpaper_)
188    return current_wallpaper_->layout();
189  return WALLPAPER_LAYOUT_CENTER_CROPPED;
190}
191
192void DesktopBackgroundController::OnRootWindowAdded(aura::Window* root_window) {
193  // The background hasn't been set yet.
194  if (desktop_background_mode_ == BACKGROUND_NONE)
195    return;
196
197  // Handle resolution change for "built-in" images.
198  gfx::Size max_display_size = GetMaxDisplaySizeInNative();
199  if (current_max_display_size_ != max_display_size) {
200    current_max_display_size_ = max_display_size;
201    if (desktop_background_mode_ == BACKGROUND_IMAGE &&
202        current_wallpaper_.get())
203      UpdateWallpaper();
204  }
205
206  InstallDesktopController(root_window);
207}
208
209bool DesktopBackgroundController::SetDefaultWallpaper(bool is_guest) {
210  const bool use_large =
211      GetAppropriateResolution() == WALLPAPER_RESOLUTION_LARGE;
212
213  base::FilePath file_path;
214  WallpaperLayout file_layout = use_large ? WALLPAPER_LAYOUT_CENTER_CROPPED :
215      WALLPAPER_LAYOUT_CENTER;
216  int resource_id = use_large ? IDR_AURA_WALLPAPER_DEFAULT_LARGE :
217      IDR_AURA_WALLPAPER_DEFAULT_SMALL;
218  WallpaperLayout resource_layout = WALLPAPER_LAYOUT_TILE;
219
220  CommandLine* command_line = command_line_for_testing_ ?
221      command_line_for_testing_ : CommandLine::ForCurrentProcess();
222  const char* switch_name = NULL;
223  if (is_guest) {
224    switch_name = use_large ? switches::kAshGuestWallpaperLarge :
225        switches::kAshGuestWallpaperSmall;
226  } else {
227    switch_name = use_large ? switches::kAshDefaultWallpaperLarge :
228        switches::kAshDefaultWallpaperSmall;
229  }
230  file_path = command_line->GetSwitchValuePath(switch_name);
231
232  if (DefaultWallpaperIsAlreadyLoadingOrLoaded(file_path, resource_id))
233    return false;
234
235  CancelPendingWallpaperOperation();
236  wallpaper_loader_ = new WallpaperLoader(
237      file_path, file_layout, resource_id, resource_layout);
238  base::WorkerPool::PostTaskAndReply(
239      FROM_HERE,
240      base::Bind(&WallpaperLoader::LoadOnWorkerPoolThread, wallpaper_loader_),
241      base::Bind(&DesktopBackgroundController::OnDefaultWallpaperLoadCompleted,
242                 weak_ptr_factory_.GetWeakPtr(),
243                 wallpaper_loader_),
244      true /* task_is_slow */);
245  return true;
246}
247
248void DesktopBackgroundController::SetCustomWallpaper(
249    const gfx::ImageSkia& image,
250    WallpaperLayout layout) {
251  CancelPendingWallpaperOperation();
252  if (CustomWallpaperIsAlreadyLoaded(image))
253    return;
254
255  current_wallpaper_.reset(new WallpaperResizer(
256      image, GetMaxDisplaySizeInNative(), layout));
257  current_wallpaper_->StartResize();
258
259  current_default_wallpaper_path_ = base::FilePath();
260  current_default_wallpaper_resource_id_ = -1;
261
262  FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver, observers_,
263                    OnWallpaperDataChanged());
264  SetDesktopBackgroundImageMode();
265}
266
267void DesktopBackgroundController::CancelPendingWallpaperOperation() {
268  // Set canceled flag of previous request to skip unneeded loading.
269  if (wallpaper_loader_.get())
270    wallpaper_loader_->Cancel();
271
272  // Cancel reply callback for previous request.
273  weak_ptr_factory_.InvalidateWeakPtrs();
274}
275
276void DesktopBackgroundController::CreateEmptyWallpaper() {
277  current_wallpaper_.reset(NULL);
278  SetDesktopBackgroundImageMode();
279}
280
281WallpaperResolution DesktopBackgroundController::GetAppropriateResolution() {
282  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
283  gfx::Size size = GetMaxDisplaySizeInNative();
284  return (size.width() > kSmallWallpaperMaxWidth ||
285          size.height() > kSmallWallpaperMaxHeight) ?
286      WALLPAPER_RESOLUTION_LARGE : WALLPAPER_RESOLUTION_SMALL;
287}
288
289bool DesktopBackgroundController::MoveDesktopToLockedContainer() {
290  if (locked_)
291    return false;
292  locked_ = true;
293  return ReparentBackgroundWidgets(GetBackgroundContainerId(false),
294                                   GetBackgroundContainerId(true));
295}
296
297bool DesktopBackgroundController::MoveDesktopToUnlockedContainer() {
298  if (!locked_)
299    return false;
300  locked_ = false;
301  return ReparentBackgroundWidgets(GetBackgroundContainerId(true),
302                                   GetBackgroundContainerId(false));
303}
304
305void DesktopBackgroundController::OnDisplayConfigurationChanged() {
306  gfx::Size max_display_size = GetMaxDisplaySizeInNative();
307  if (current_max_display_size_ != max_display_size) {
308    current_max_display_size_ = max_display_size;
309    if (desktop_background_mode_ == BACKGROUND_IMAGE &&
310        current_wallpaper_.get()) {
311      timer_.Stop();
312      timer_.Start(FROM_HERE,
313                   base::TimeDelta::FromMilliseconds(wallpaper_reload_delay_),
314                   this,
315                   &DesktopBackgroundController::UpdateWallpaper);
316    }
317  }
318}
319
320bool DesktopBackgroundController::DefaultWallpaperIsAlreadyLoadingOrLoaded(
321    const base::FilePath& image_file, int image_resource_id) const {
322  return (wallpaper_loader_.get() &&
323          wallpaper_loader_->file_path() == image_file &&
324          wallpaper_loader_->resource_id() == image_resource_id) ||
325         (current_wallpaper_.get() &&
326          current_default_wallpaper_path_ == image_file &&
327          current_default_wallpaper_resource_id_ == image_resource_id);
328}
329
330bool DesktopBackgroundController::CustomWallpaperIsAlreadyLoaded(
331    const gfx::ImageSkia& image) const {
332  return current_wallpaper_.get() &&
333      (WallpaperResizer::GetImageId(image) ==
334       current_wallpaper_->original_image_id());
335}
336
337void DesktopBackgroundController::SetDesktopBackgroundImageMode() {
338  desktop_background_mode_ = BACKGROUND_IMAGE;
339  InstallDesktopControllerForAllWindows();
340}
341
342void DesktopBackgroundController::OnDefaultWallpaperLoadCompleted(
343    scoped_refptr<WallpaperLoader> loader) {
344  current_wallpaper_.reset(loader->ReleaseWallpaperResizer());
345  current_wallpaper_->StartResize();
346  current_default_wallpaper_path_ = loader->file_path();
347  current_default_wallpaper_resource_id_ = loader->resource_id();
348  FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver, observers_,
349                    OnWallpaperDataChanged());
350
351  SetDesktopBackgroundImageMode();
352
353  DCHECK(loader.get() == wallpaper_loader_.get());
354  wallpaper_loader_ = NULL;
355}
356
357void DesktopBackgroundController::InstallDesktopController(
358    aura::Window* root_window) {
359  internal::DesktopBackgroundWidgetController* component = NULL;
360  int container_id = GetBackgroundContainerId(locked_);
361
362  switch (desktop_background_mode_) {
363    case BACKGROUND_IMAGE: {
364      views::Widget* widget = internal::CreateDesktopBackground(root_window,
365                                                                container_id);
366      component = new internal::DesktopBackgroundWidgetController(widget);
367      break;
368    }
369    case BACKGROUND_NONE:
370      NOTREACHED();
371      return;
372  }
373  internal::GetRootWindowController(root_window)->
374      SetAnimatingWallpaperController(
375          new internal::AnimatingDesktopController(component));
376
377  component->StartAnimating(internal::GetRootWindowController(root_window));
378}
379
380void DesktopBackgroundController::InstallDesktopControllerForAllWindows() {
381  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
382  for (aura::Window::Windows::iterator iter = root_windows.begin();
383       iter != root_windows.end(); ++iter) {
384    InstallDesktopController(*iter);
385  }
386  current_max_display_size_ = GetMaxDisplaySizeInNative();
387}
388
389bool DesktopBackgroundController::ReparentBackgroundWidgets(int src_container,
390                                                            int dst_container) {
391  bool moved = false;
392  Shell::RootWindowControllerList controllers =
393      Shell::GetAllRootWindowControllers();
394  for (Shell::RootWindowControllerList::iterator iter = controllers.begin();
395    iter != controllers.end(); ++iter) {
396    internal::RootWindowController* root_window_controller = *iter;
397    // In the steady state (no animation playing) the background widget
398    // controller exists in the RootWindowController.
399    DesktopBackgroundWidgetController* desktop_controller =
400        root_window_controller->wallpaper_controller();
401    if (desktop_controller) {
402      moved |= desktop_controller->Reparent(
403          root_window_controller->root_window(),
404          src_container,
405          dst_container);
406    }
407    // During desktop show animations the controller lives in
408    // AnimatingDesktopController owned by RootWindowController.
409    // NOTE: If a wallpaper load happens during a desktop show animation there
410    // can temporarily be two desktop background widgets.  We must reparent
411    // both of them - one above and one here.
412    DesktopBackgroundWidgetController* animating_controller =
413        root_window_controller->animating_wallpaper_controller() ?
414        root_window_controller->animating_wallpaper_controller()->
415            GetController(false) :
416        NULL;
417    if (animating_controller) {
418      moved |= animating_controller->Reparent(
419          root_window_controller->root_window(),
420          src_container,
421          dst_container);
422    }
423  }
424  return moved;
425}
426
427int DesktopBackgroundController::GetBackgroundContainerId(bool locked) {
428  return locked ? internal::kShellWindowId_LockScreenBackgroundContainer :
429                  internal::kShellWindowId_DesktopBackgroundContainer;
430}
431
432void DesktopBackgroundController::UpdateWallpaper() {
433  current_wallpaper_.reset(NULL);
434  current_default_wallpaper_path_ = base::FilePath();
435  current_default_wallpaper_resource_id_ = -1;
436  ash::Shell::GetInstance()->user_wallpaper_delegate()->
437      UpdateWallpaper();
438}
439
440// static
441gfx::Size DesktopBackgroundController::GetMaxDisplaySizeInNative() {
442  int width = 0;
443  int height = 0;
444  std::vector<gfx::Display> displays = Shell::GetScreen()->GetAllDisplays();
445  internal::DisplayManager* display_manager =
446      Shell::GetInstance()->display_manager();
447
448  for (std::vector<gfx::Display>::iterator iter = displays.begin();
449       iter != displays.end(); ++iter) {
450    // Don't use size_in_pixel because we want to use the native pixel size.
451    gfx::Size size_in_pixel =
452        display_manager->GetDisplayInfo(iter->id()).bounds_in_native().size();
453    if (iter->rotation() == gfx::Display::ROTATE_90 ||
454        iter->rotation() == gfx::Display::ROTATE_270) {
455      size_in_pixel = gfx::Size(size_in_pixel.height(), size_in_pixel.width());
456    }
457    width = std::max(size_in_pixel.width(), width);
458    height = std::max(size_in_pixel.height(), height);
459  }
460  return gfx::Size(width, height);
461}
462
463}  // namespace ash
464