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