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.
5 #include "ash/desktop_background/desktop_background_controller.h"
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/root_window_layout_manager.h"
18 #include "base/bind.h"
19 #include "base/command_line.h"
20 #include "base/file_util.h"
21 #include "base/logging.h"
22 #include "base/synchronization/cancellation_flag.h"
23 #include "base/threading/worker_pool.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "grit/ash_wallpaper_resources.h"
26 #include "ui/aura/root_window.h"
27 #include "ui/aura/window.h"
28 #include "ui/compositor/layer.h"
29 #include "ui/gfx/codec/jpeg_codec.h"
30 #include "ui/gfx/image/image_skia.h"
31 #include "ui/gfx/rect.h"
32 #include "ui/views/widget/widget.h"
34 using ash::internal::DesktopBackgroundWidgetController
;
35 using ash::internal::kAnimatingDesktopController
;
36 using ash::internal::kDesktopController
;
37 using content::BrowserThread
;
42 const SkColor kTransparentColor
= SkColorSetARGB(0x00, 0x00, 0x00, 0x00);
44 internal::RootWindowLayoutManager
* GetRootWindowLayoutManager(
45 aura::RootWindow
* root_window
) {
46 return static_cast<internal::RootWindowLayoutManager
*>(
47 root_window
->layout_manager());
50 // Returns the maximum width and height of all root windows.
51 gfx::Size
GetRootWindowsSize() {
54 Shell::RootWindowList root_windows
= Shell::GetAllRootWindows();
55 for (Shell::RootWindowList::iterator iter
= root_windows
.begin();
56 iter
!= root_windows
.end(); ++iter
) {
57 gfx::Size root_window_size
= (*iter
)->GetHostSize();
58 if (root_window_size
.width() > width
)
59 width
= root_window_size
.width();
60 if (root_window_size
.height() > height
)
61 height
= root_window_size
.height();
63 return gfx::Size(width
, height
);
68 const int kSmallWallpaperMaxWidth
= 1366;
69 const int kSmallWallpaperMaxHeight
= 800;
70 const int kLargeWallpaperMaxWidth
= 2560;
71 const int kLargeWallpaperMaxHeight
= 1700;
72 const int kWallpaperThumbnailWidth
= 108;
73 const int kWallpaperThumbnailHeight
= 68;
75 // DesktopBackgroundController::WallpaperLoader wraps background wallpaper
77 class DesktopBackgroundController::WallpaperLoader
78 : public base::RefCountedThreadSafe
<
79 DesktopBackgroundController::WallpaperLoader
> {
81 // If set, |file_path| must be a trusted (i.e. read-only,
82 // non-user-controlled) file containing a JPEG image.
83 WallpaperLoader(const base::FilePath
& file_path
,
84 WallpaperLayout file_layout
,
86 WallpaperLayout resource_layout
)
87 : file_path_(file_path
),
88 file_layout_(file_layout
),
89 resource_id_(resource_id
),
90 resource_layout_(resource_layout
) {
93 static void LoadOnWorkerPoolThread(scoped_refptr
<WallpaperLoader
> loader
) {
94 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI
));
95 loader
->LoadWallpaper();
98 const base::FilePath
& file_path() const { return file_path_
; }
99 int resource_id() const { return resource_id_
; }
105 WallpaperResizer
* ReleaseWallpaperResizer() {
106 return wallpaper_resizer_
.release();
110 friend class base::RefCountedThreadSafe
<
111 DesktopBackgroundController::WallpaperLoader
>;
113 // Loads a JPEG image from |path|, a trusted file -- note that the image
114 // is not loaded in a sandboxed process. Returns an empty pointer on
116 static scoped_ptr
<SkBitmap
> LoadSkBitmapFromJPEGFile(
117 const base::FilePath
& path
) {
119 if (!file_util::ReadFileToString(path
, &data
)) {
120 LOG(ERROR
) << "Unable to read data from " << path
.value();
121 return scoped_ptr
<SkBitmap
>();
124 scoped_ptr
<SkBitmap
> bitmap(gfx::JPEGCodec::Decode(
125 reinterpret_cast<const unsigned char*>(data
.data()), data
.size()));
127 LOG(ERROR
) << "Unable to decode JPEG data from " << path
.value();
128 return bitmap
.Pass();
131 void LoadWallpaper() {
132 if (cancel_flag_
.IsSet())
135 if (!file_path_
.empty())
136 file_bitmap_
= LoadSkBitmapFromJPEGFile(file_path_
);
138 if (cancel_flag_
.IsSet())
142 gfx::ImageSkia image
= gfx::ImageSkia::CreateFrom1xBitmap(*file_bitmap_
);
143 wallpaper_resizer_
.reset(new WallpaperResizer(
144 image
, GetRootWindowsSize(), file_layout_
));
146 wallpaper_resizer_
.reset(new WallpaperResizer(
147 resource_id_
, GetRootWindowsSize(), resource_layout_
));
151 ~WallpaperLoader() {}
153 base::CancellationFlag cancel_flag_
;
155 // Bitmap loaded from |file_path_|.
156 scoped_ptr
<SkBitmap
> file_bitmap_
;
158 scoped_ptr
<WallpaperResizer
> wallpaper_resizer_
;
160 // Path to a trusted JPEG file.
161 base::FilePath file_path_
;
163 // Layout to be used when displaying the image from |file_path_|.
164 WallpaperLayout file_layout_
;
166 // ID of an image resource to use if |file_path_| is empty or unloadable.
169 // Layout to be used when displaying |resource_id_|.
170 WallpaperLayout resource_layout_
;
172 DISALLOW_COPY_AND_ASSIGN(WallpaperLoader
);
175 DesktopBackgroundController::DesktopBackgroundController()
176 : command_line_for_testing_(NULL
),
178 desktop_background_mode_(BACKGROUND_NONE
),
179 background_color_(kTransparentColor
),
180 current_default_wallpaper_resource_id_(-1),
181 weak_ptr_factory_(this) {
184 DesktopBackgroundController::~DesktopBackgroundController() {
185 CancelPendingWallpaperOperation();
188 gfx::ImageSkia
DesktopBackgroundController::GetWallpaper() const {
189 if (current_wallpaper_
)
190 return current_wallpaper_
->wallpaper_image();
191 return gfx::ImageSkia();
194 void DesktopBackgroundController::AddObserver(
195 DesktopBackgroundControllerObserver
* observer
) {
196 observers_
.AddObserver(observer
);
199 void DesktopBackgroundController::RemoveObserver(
200 DesktopBackgroundControllerObserver
* observer
) {
201 observers_
.RemoveObserver(observer
);
204 WallpaperLayout
DesktopBackgroundController::GetWallpaperLayout() const {
205 if (current_wallpaper_
)
206 return current_wallpaper_
->layout();
207 return WALLPAPER_LAYOUT_CENTER_CROPPED
;
210 void DesktopBackgroundController::OnRootWindowAdded(
211 aura::RootWindow
* root_window
) {
212 // The background hasn't been set yet.
213 if (desktop_background_mode_
== BACKGROUND_NONE
)
216 // Handle resolution change for "built-in" images.
217 if (BACKGROUND_IMAGE
== desktop_background_mode_
&&
218 current_wallpaper_
.get()) {
219 gfx::Size root_window_size
= root_window
->GetHostSize();
220 int width
= current_wallpaper_
->wallpaper_image().width();
221 int height
= current_wallpaper_
->wallpaper_image().height();
222 // Reloads wallpaper if current wallpaper is smaller than the new added root
224 if (width
< root_window_size
.width() ||
225 height
< root_window_size
.height()) {
226 current_wallpaper_
.reset(NULL
);
227 current_default_wallpaper_path_
= base::FilePath();
228 current_default_wallpaper_resource_id_
= -1;
229 ash::Shell::GetInstance()->user_wallpaper_delegate()->
234 InstallDesktopController(root_window
);
237 bool DesktopBackgroundController::SetDefaultWallpaper(bool is_guest
) {
238 const bool use_large
=
239 GetAppropriateResolution() == WALLPAPER_RESOLUTION_LARGE
;
241 base::FilePath file_path
;
242 WallpaperLayout file_layout
= use_large
? WALLPAPER_LAYOUT_CENTER_CROPPED
:
243 WALLPAPER_LAYOUT_CENTER
;
244 int resource_id
= -1;
245 WallpaperLayout resource_layout
= WALLPAPER_LAYOUT_TILE
;
247 #if defined(GOOGLE_CHROME_BUILD)
249 resource_id
= is_guest
? IDR_AURA_WALLPAPERS_2_LANDSCAPE7_LARGE
:
250 IDR_AURA_WALLPAPERS_2_LANDSCAPE8_LARGE
;
251 resource_layout
= WALLPAPER_LAYOUT_CENTER_CROPPED
;
253 resource_id
= is_guest
? IDR_AURA_WALLPAPERS_2_LANDSCAPE7_SMALL
:
254 IDR_AURA_WALLPAPERS_2_LANDSCAPE8_SMALL
;
255 resource_layout
= WALLPAPER_LAYOUT_CENTER
;
258 resource_id
= use_large
? IDR_AURA_WALLPAPERS_5_GRADIENT5_LARGE
:
259 IDR_AURA_WALLPAPERS_5_GRADIENT5_SMALL
;
260 resource_layout
= WALLPAPER_LAYOUT_TILE
;
263 const char* switch_name
= is_guest
?
264 (use_large
? switches::kAshDefaultGuestWallpaperLarge
:
265 switches::kAshDefaultGuestWallpaperSmall
) :
266 (use_large
? switches::kAshDefaultWallpaperLarge
:
267 switches::kAshDefaultWallpaperSmall
);
268 CommandLine
* command_line
= command_line_for_testing_
?
269 command_line_for_testing_
: CommandLine::ForCurrentProcess();
270 file_path
= command_line
->GetSwitchValuePath(switch_name
);
272 if (DefaultWallpaperIsAlreadyLoadingOrLoaded(file_path
, resource_id
))
275 CancelPendingWallpaperOperation();
276 wallpaper_loader_
= new WallpaperLoader(
277 file_path
, file_layout
, resource_id
, resource_layout
);
278 base::WorkerPool::PostTaskAndReply(
280 base::Bind(&WallpaperLoader::LoadOnWorkerPoolThread
, wallpaper_loader_
),
281 base::Bind(&DesktopBackgroundController::OnDefaultWallpaperLoadCompleted
,
282 weak_ptr_factory_
.GetWeakPtr(),
284 true /* task_is_slow */);
288 void DesktopBackgroundController::SetCustomWallpaper(
289 const gfx::ImageSkia
& image
,
290 WallpaperLayout layout
) {
291 CancelPendingWallpaperOperation();
292 if (CustomWallpaperIsAlreadyLoaded(image
))
295 current_wallpaper_
.reset(new WallpaperResizer(
296 image
, GetRootWindowsSize(), layout
));
297 current_wallpaper_
->StartResize();
299 current_default_wallpaper_path_
= base::FilePath();
300 current_default_wallpaper_resource_id_
= -1;
302 FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver
, observers_
,
303 OnWallpaperDataChanged());
304 SetDesktopBackgroundImageMode();
307 void DesktopBackgroundController::CancelPendingWallpaperOperation() {
308 // Set canceled flag of previous request to skip unneeded loading.
309 if (wallpaper_loader_
.get())
310 wallpaper_loader_
->Cancel();
312 // Cancel reply callback for previous request.
313 weak_ptr_factory_
.InvalidateWeakPtrs();
316 void DesktopBackgroundController::SetDesktopBackgroundSolidColorMode(
318 background_color_
= color
;
319 desktop_background_mode_
= BACKGROUND_SOLID_COLOR
;
321 InstallDesktopControllerForAllWindows();
324 void DesktopBackgroundController::CreateEmptyWallpaper() {
325 current_wallpaper_
.reset(NULL
);
326 SetDesktopBackgroundImageMode();
329 WallpaperResolution
DesktopBackgroundController::GetAppropriateResolution() {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
331 Shell::RootWindowList root_windows
= Shell::GetAllRootWindows();
332 for (Shell::RootWindowList::iterator iter
= root_windows
.begin();
333 iter
!= root_windows
.end(); ++iter
) {
334 // Compare to host size as constants are defined in terms of
335 // physical pixel size.
336 // TODO(oshima): This may not be ideal for fractional scaling
337 // scenario. Revisit and fix if necessary.
338 gfx::Size host_window_size
= (*iter
)->GetHostSize();
339 if (host_window_size
.width() > kSmallWallpaperMaxWidth
||
340 host_window_size
.height() > kSmallWallpaperMaxHeight
)
341 return WALLPAPER_RESOLUTION_LARGE
;
343 return WALLPAPER_RESOLUTION_SMALL
;
346 bool DesktopBackgroundController::MoveDesktopToLockedContainer() {
350 return ReparentBackgroundWidgets(GetBackgroundContainerId(false),
351 GetBackgroundContainerId(true));
354 bool DesktopBackgroundController::MoveDesktopToUnlockedContainer() {
358 return ReparentBackgroundWidgets(GetBackgroundContainerId(true),
359 GetBackgroundContainerId(false));
362 void DesktopBackgroundController::OnWindowDestroying(aura::Window
* window
) {
363 window
->SetProperty(kDesktopController
,
364 static_cast<internal::DesktopBackgroundWidgetController
*>(NULL
));
365 window
->SetProperty(kAnimatingDesktopController
,
366 static_cast<internal::AnimatingDesktopController
*>(NULL
));
369 bool DesktopBackgroundController::DefaultWallpaperIsAlreadyLoadingOrLoaded(
370 const base::FilePath
& image_file
, int image_resource_id
) const {
371 return (wallpaper_loader_
&&
372 wallpaper_loader_
->file_path() == image_file
&&
373 wallpaper_loader_
->resource_id() == image_resource_id
) ||
374 (current_wallpaper_
.get() &&
375 current_default_wallpaper_path_
== image_file
&&
376 current_default_wallpaper_resource_id_
== image_resource_id
);
379 bool DesktopBackgroundController::CustomWallpaperIsAlreadyLoaded(
380 const gfx::ImageSkia
& image
) const {
381 return current_wallpaper_
.get() &&
382 current_wallpaper_
->wallpaper_image().BackedBySameObjectAs(image
);
385 void DesktopBackgroundController::SetDesktopBackgroundImageMode() {
386 desktop_background_mode_
= BACKGROUND_IMAGE
;
387 InstallDesktopControllerForAllWindows();
390 void DesktopBackgroundController::OnDefaultWallpaperLoadCompleted(
391 scoped_refptr
<WallpaperLoader
> loader
) {
392 current_wallpaper_
.reset(loader
->ReleaseWallpaperResizer());
393 current_wallpaper_
->StartResize();
394 current_default_wallpaper_path_
= loader
->file_path();
395 current_default_wallpaper_resource_id_
= loader
->resource_id();
396 FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver
, observers_
,
397 OnWallpaperDataChanged());
399 SetDesktopBackgroundImageMode();
401 DCHECK(loader
.get() == wallpaper_loader_
.get());
402 wallpaper_loader_
= NULL
;
405 void DesktopBackgroundController::NotifyAnimationFinished() {
406 Shell
* shell
= Shell::GetInstance();
407 shell
->GetPrimaryRootWindowController()->HandleDesktopBackgroundVisible();
408 shell
->user_wallpaper_delegate()->OnWallpaperAnimationFinished();
411 ui::Layer
* DesktopBackgroundController::SetColorLayerForContainer(
413 aura::RootWindow
* root_window
,
415 ui::Layer
* background_layer
= new ui::Layer(ui::LAYER_SOLID_COLOR
);
416 background_layer
->SetColor(color
);
418 Shell::GetContainer(root_window
,container_id
)->
419 layer()->Add(background_layer
);
421 base::MessageLoop::current()->PostTask(
423 base::Bind(&DesktopBackgroundController::NotifyAnimationFinished
,
424 weak_ptr_factory_
.GetWeakPtr()));
426 return background_layer
;
429 void DesktopBackgroundController::InstallDesktopController(
430 aura::RootWindow
* root_window
) {
431 internal::DesktopBackgroundWidgetController
* component
= NULL
;
432 int container_id
= GetBackgroundContainerId(locked_
);
434 switch (desktop_background_mode_
) {
435 case BACKGROUND_IMAGE
: {
436 views::Widget
* widget
= internal::CreateDesktopBackground(root_window
,
438 component
= new internal::DesktopBackgroundWidgetController(widget
);
441 case BACKGROUND_SOLID_COLOR
: {
442 ui::Layer
* layer
= SetColorLayerForContainer(background_color_
,
445 component
= new internal::DesktopBackgroundWidgetController(layer
);
448 case BACKGROUND_NONE
:
452 // Ensure we're only observing the root window once. Don't rely on a window
453 // property check as those can be cleared by tests resetting the background.
454 if (!root_window
->HasObserver(this))
455 root_window
->AddObserver(this);
457 internal::AnimatingDesktopController
* animating_controller
=
458 root_window
->GetProperty(kAnimatingDesktopController
);
459 if (animating_controller
)
460 animating_controller
->StopAnimating();
461 root_window
->SetProperty(kAnimatingDesktopController
,
462 new internal::AnimatingDesktopController(component
));
465 void DesktopBackgroundController::InstallDesktopControllerForAllWindows() {
466 Shell::RootWindowList root_windows
= Shell::GetAllRootWindows();
467 for (Shell::RootWindowList::iterator iter
= root_windows
.begin();
468 iter
!= root_windows
.end(); ++iter
) {
469 InstallDesktopController(*iter
);
473 bool DesktopBackgroundController::ReparentBackgroundWidgets(int src_container
,
476 Shell::RootWindowList root_windows
= Shell::GetAllRootWindows();
477 for (Shell::RootWindowList::iterator iter
= root_windows
.begin();
478 iter
!= root_windows
.end(); ++iter
) {
479 aura::RootWindow
* root_window
= *iter
;
480 // In the steady state (no animation playing) the background widget
481 // controller exists in the kDesktopController property.
482 DesktopBackgroundWidgetController
* desktop_controller
= root_window
->
483 GetProperty(kDesktopController
);
484 if (desktop_controller
) {
485 moved
|= desktop_controller
->Reparent(root_window
,
489 // During desktop show animations the controller lives in
490 // kAnimatingDesktopController.
491 // NOTE: If a wallpaper load happens during a desktop show animation there
492 // can temporarily be two desktop background widgets. We must reparent
493 // both of them - one above and one here.
494 DesktopBackgroundWidgetController
* animating_controller
=
495 root_window
->GetProperty(kAnimatingDesktopController
) ?
496 root_window
->GetProperty(kAnimatingDesktopController
)->
497 GetController(false) :
499 if (animating_controller
) {
500 moved
|= animating_controller
->Reparent(root_window
,
508 int DesktopBackgroundController::GetBackgroundContainerId(bool locked
) {
509 return locked
? internal::kShellWindowId_LockScreenBackgroundContainer
:
510 internal::kShellWindowId_DesktopBackgroundContainer
;