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 <cmath>
8#include <cstdlib>
9
10#include "ash/ash_switches.h"
11#include "ash/desktop_background/desktop_background_controller_observer.h"
12#include "ash/desktop_background/desktop_background_widget_controller.h"
13#include "ash/root_window_controller.h"
14#include "ash/shell.h"
15#include "ash/shell_window_ids.h"
16#include "ash/test/ash_test_base.h"
17#include "ash/test/display_manager_test_api.h"
18#include "base/command_line.h"
19#include "base/file_util.h"
20#include "base/files/file_path.h"
21#include "base/files/scoped_temp_dir.h"
22#include "base/message_loop/message_loop.h"
23#include "content/public/test/test_browser_thread.h"
24#include "third_party/skia/include/core/SkBitmap.h"
25#include "third_party/skia/include/core/SkColor.h"
26#include "ui/aura/root_window.h"
27#include "ui/compositor/scoped_animation_duration_scale_mode.h"
28#include "ui/compositor/test/layer_animator_test_controller.h"
29#include "ui/gfx/codec/jpeg_codec.h"
30#include "ui/gfx/point.h"
31#include "ui/gfx/rect.h"
32
33using aura::RootWindow;
34using aura::Window;
35
36namespace ash {
37namespace internal {
38
39namespace {
40
41// Containers IDs used for tests.
42const int kDesktopBackgroundId =
43    ash::internal::kShellWindowId_DesktopBackgroundContainer;
44const int kLockScreenBackgroundId =
45    ash::internal::kShellWindowId_LockScreenBackgroundContainer;
46
47// Returns number of child windows in a shell window container.
48int ChildCountForContainer(int container_id) {
49  RootWindow* root = ash::Shell::GetPrimaryRootWindow();
50  Window* container = root->GetChildById(container_id);
51  return static_cast<int>(container->children().size());
52}
53
54class TestObserver : public DesktopBackgroundControllerObserver {
55 public:
56  explicit TestObserver(DesktopBackgroundController* controller)
57      : controller_(controller) {
58    DCHECK(controller_);
59    controller_->AddObserver(this);
60  }
61
62  virtual ~TestObserver() {
63    controller_->RemoveObserver(this);
64  }
65
66  void WaitForWallpaperDataChanged() {
67    base::MessageLoop::current()->Run();
68  }
69
70  // DesktopBackgroundControllerObserver overrides:
71  virtual void OnWallpaperDataChanged() OVERRIDE {
72    base::MessageLoop::current()->Quit();
73  }
74
75 private:
76  DesktopBackgroundController* controller_;
77};
78
79// Steps a widget's layer animation until it is completed. Animations must be
80// enabled.
81void RunAnimationForWidget(views::Widget* widget) {
82  // Animations must be enabled for stepping to work.
83  ASSERT_NE(ui::ScopedAnimationDurationScaleMode::duration_scale_mode(),
84            ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
85
86  ui::Layer* layer = widget->GetNativeView()->layer();
87  ui::LayerAnimatorTestController controller(layer->GetAnimator());
88  ui::AnimationContainerElement* element = layer->GetAnimator();
89  // Multiple steps are required to complete complex animations.
90  // TODO(vollick): This should not be necessary. crbug.com/154017
91  while (controller.animator()->is_animating()) {
92    controller.StartThreadedAnimationsIfNeeded();
93    base::TimeTicks step_time = controller.animator()->last_step_time();
94    element->Step(step_time + base::TimeDelta::FromMilliseconds(1000));
95  }
96}
97
98}  // namespace
99
100class DesktopBackgroundControllerTest : public test::AshTestBase {
101 public:
102  DesktopBackgroundControllerTest()
103      : command_line_(CommandLine::NO_PROGRAM),
104        controller_(NULL) {
105  }
106  virtual ~DesktopBackgroundControllerTest() {}
107
108  virtual void SetUp() OVERRIDE {
109    test::AshTestBase::SetUp();
110    // Ash shell initialization creates wallpaper. Reset it so we can manually
111    // control wallpaper creation and animation in our tests.
112    RootWindowController* root_window_controller =
113        Shell::GetPrimaryRootWindowController();
114    root_window_controller->SetWallpaperController(NULL);
115    root_window_controller->SetAnimatingWallpaperController(NULL);
116    controller_ = Shell::GetInstance()->desktop_background_controller();
117  }
118
119 protected:
120  // Colors used for different default wallpapers by
121  // WriteWallpapersAndSetFlags().
122  static const SkColor kLargeWallpaperColor = SK_ColorRED;
123  static const SkColor kSmallWallpaperColor = SK_ColorGREEN;
124  static const SkColor kLargeGuestWallpaperColor = SK_ColorBLUE;
125  static const SkColor kSmallGuestWallpaperColor = SK_ColorYELLOW;
126
127  // Dimension used for width and height of default wallpaper images. A
128  // small value is used to minimize the amount of time spent compressing
129  // and writing images.
130  static const int kWallpaperSize = 2;
131
132  // Runs kAnimatingDesktopController's animation to completion.
133  // TODO(bshe): Don't require tests to run animations; it's slow.
134  void RunDesktopControllerAnimation() {
135    DesktopBackgroundWidgetController* controller =
136        Shell::GetPrimaryRootWindowController()->
137        animating_wallpaper_controller()->GetController(false);
138    ASSERT_NO_FATAL_FAILURE(RunAnimationForWidget(controller->widget()));
139  }
140
141  // Returns true if the color at the center of |image| is close to
142  // |expected_color|. (The center is used so small wallpaper images can be
143  // used.)
144  bool ImageIsNearColor(gfx::ImageSkia image, SkColor expected_color) {
145    if (image.size().IsEmpty()) {
146      LOG(ERROR) << "Image is empty";
147      return false;
148    }
149
150    const SkBitmap* bitmap = image.bitmap();
151    if (!bitmap) {
152      LOG(ERROR) << "Unable to get bitmap from image";
153      return false;
154    }
155
156    bitmap->lockPixels();
157    gfx::Point center = gfx::Rect(image.size()).CenterPoint();
158    SkColor image_color = bitmap->getColor(center.x(), center.y());
159    bitmap->unlockPixels();
160
161    const int kDiff = 3;
162    if (std::abs(static_cast<int>(SkColorGetA(image_color)) -
163                 static_cast<int>(SkColorGetA(expected_color))) > kDiff ||
164        std::abs(static_cast<int>(SkColorGetR(image_color)) -
165                 static_cast<int>(SkColorGetR(expected_color))) > kDiff ||
166        std::abs(static_cast<int>(SkColorGetG(image_color)) -
167                 static_cast<int>(SkColorGetG(expected_color))) > kDiff ||
168        std::abs(static_cast<int>(SkColorGetB(image_color)) -
169                 static_cast<int>(SkColorGetB(expected_color))) > kDiff) {
170      LOG(ERROR) << "Expected color near 0x" << std::hex << expected_color
171                 << " but got 0x" << image_color;
172      return false;
173    }
174
175    return true;
176  }
177
178  // Writes a JPEG image of the specified size and color to |path|. Returns
179  // true on success.
180  bool WriteJPEGFile(const base::FilePath& path,
181                     int width,
182                     int height,
183                     SkColor color) {
184    SkBitmap bitmap;
185    bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0);
186    bitmap.allocPixels();
187    bitmap.eraseColor(color);
188
189    const int kQuality = 80;
190    std::vector<unsigned char> output;
191    if (!gfx::JPEGCodec::Encode(
192            static_cast<const unsigned char*>(bitmap.getPixels()),
193            gfx::JPEGCodec::FORMAT_SkBitmap, width, height, bitmap.rowBytes(),
194            kQuality, &output)) {
195      LOG(ERROR) << "Unable to encode " << width << "x" << height << " bitmap";
196      return false;
197    }
198
199    size_t bytes_written = file_util::WriteFile(
200        path, reinterpret_cast<const char*>(&output[0]), output.size());
201    if (bytes_written != output.size()) {
202      LOG(ERROR) << "Wrote " << bytes_written << " byte(s) instead of "
203                 << output.size() << " to " << path.value();
204      return false;
205    }
206
207    return true;
208  }
209
210  // Initializes |wallpaper_dir_|, writes JPEG wallpaper images to it, and
211  // passes |controller_| a command line instructing it to use the images.
212  // Only needs to be called (once) by tests that want to test loading of
213  // default wallpapers.
214  void WriteWallpapersAndSetFlags() {
215    wallpaper_dir_.reset(new base::ScopedTempDir);
216    ASSERT_TRUE(wallpaper_dir_->CreateUniqueTempDir());
217
218    const base::FilePath kLargePath =
219        wallpaper_dir_->path().Append(FILE_PATH_LITERAL("large.jpg"));
220    ASSERT_TRUE(WriteJPEGFile(kLargePath, kWallpaperSize, kWallpaperSize,
221                              kLargeWallpaperColor));
222    command_line_.AppendSwitchPath(
223        switches::kAshDefaultWallpaperLarge, kLargePath);
224
225    const base::FilePath kSmallPath =
226        wallpaper_dir_->path().Append(FILE_PATH_LITERAL("small.jpg"));
227    ASSERT_TRUE(WriteJPEGFile(kSmallPath, kWallpaperSize, kWallpaperSize,
228                              kSmallWallpaperColor));
229    command_line_.AppendSwitchPath(
230        switches::kAshDefaultWallpaperSmall, kSmallPath);
231
232    const base::FilePath kLargeGuestPath =
233        wallpaper_dir_->path().Append(FILE_PATH_LITERAL("guest_large.jpg"));
234    ASSERT_TRUE(WriteJPEGFile(kLargeGuestPath, kWallpaperSize, kWallpaperSize,
235                              kLargeGuestWallpaperColor));
236    command_line_.AppendSwitchPath(
237        switches::kAshDefaultGuestWallpaperLarge, kLargeGuestPath);
238
239    const base::FilePath kSmallGuestPath =
240        wallpaper_dir_->path().Append(FILE_PATH_LITERAL("guest_small.jpg"));
241    ASSERT_TRUE(WriteJPEGFile(kSmallGuestPath, kWallpaperSize, kWallpaperSize,
242                              kSmallGuestWallpaperColor));
243    command_line_.AppendSwitchPath(
244        switches::kAshDefaultGuestWallpaperSmall, kSmallGuestPath);
245
246    controller_->set_command_line_for_testing(&command_line_);
247  }
248
249  // Custom command line passed to DesktopBackgroundController by
250  // WriteWallpapersAndSetFlags().
251  CommandLine command_line_;
252
253  // Directory created by WriteWallpapersAndSetFlags() to store default
254  // wallpaper images.
255  scoped_ptr<base::ScopedTempDir> wallpaper_dir_;
256
257  DesktopBackgroundController* controller_;  // Not owned.
258
259 private:
260  DISALLOW_COPY_AND_ASSIGN(DesktopBackgroundControllerTest);
261};
262
263TEST_F(DesktopBackgroundControllerTest, BasicReparenting) {
264  DesktopBackgroundController* controller =
265      Shell::GetInstance()->desktop_background_controller();
266  controller->CreateEmptyWallpaper();
267
268  // Wallpaper view/window exists in the desktop background container and
269  // nothing is in the lock screen background container.
270  EXPECT_EQ(1, ChildCountForContainer(kDesktopBackgroundId));
271  EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
272
273  // Moving background to lock container should succeed the first time but
274  // subsequent calls should do nothing.
275  EXPECT_TRUE(controller->MoveDesktopToLockedContainer());
276  EXPECT_FALSE(controller->MoveDesktopToLockedContainer());
277
278  // One window is moved from desktop to lock container.
279  EXPECT_EQ(0, ChildCountForContainer(kDesktopBackgroundId));
280  EXPECT_EQ(1, ChildCountForContainer(kLockScreenBackgroundId));
281
282  // Moving background to desktop container should succeed the first time.
283  EXPECT_TRUE(controller->MoveDesktopToUnlockedContainer());
284  EXPECT_FALSE(controller->MoveDesktopToUnlockedContainer());
285
286  // One window is moved from lock to desktop container.
287  EXPECT_EQ(1, ChildCountForContainer(kDesktopBackgroundId));
288  EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
289}
290
291TEST_F(DesktopBackgroundControllerTest, ControllerOwnership) {
292  // We cannot short-circuit animations for this test.
293  ui::ScopedAnimationDurationScaleMode normal_duration_mode(
294      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
295
296  // Create wallpaper and background view.
297  DesktopBackgroundController* controller =
298      Shell::GetInstance()->desktop_background_controller();
299  controller->CreateEmptyWallpaper();
300
301  // The new wallpaper is ready to start animating. kAnimatingDesktopController
302  // holds the widget controller instance. kDesktopController will get it later.
303  RootWindowController* root_window_controller =
304      Shell::GetPrimaryRootWindowController();
305  EXPECT_TRUE(root_window_controller->animating_wallpaper_controller()->
306              GetController(false));
307
308  // kDesktopController will receive the widget controller when the animation
309  // is done.
310  EXPECT_FALSE(root_window_controller->wallpaper_controller());
311
312  // Force the widget's layer animation to play to completion.
313  RunDesktopControllerAnimation();
314
315  // Ownership has moved from kAnimatingDesktopController to kDesktopController.
316  EXPECT_FALSE(root_window_controller->animating_wallpaper_controller()->
317               GetController(false));
318  EXPECT_TRUE(root_window_controller->wallpaper_controller());
319}
320
321// Test for crbug.com/149043 "Unlock screen, no launcher appears". Ensure we
322// move all desktop views if there are more than one.
323TEST_F(DesktopBackgroundControllerTest, BackgroundMovementDuringUnlock) {
324  // We cannot short-circuit animations for this test.
325  ui::ScopedAnimationDurationScaleMode normal_duration_mode(
326      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
327
328  // Reset wallpaper state, see ControllerOwnership above.
329  DesktopBackgroundController* controller =
330      Shell::GetInstance()->desktop_background_controller();
331  controller->CreateEmptyWallpaper();
332
333  // Run wallpaper show animation to completion.
334  RunDesktopControllerAnimation();
335
336  // User locks the screen, which moves the background forward.
337  controller->MoveDesktopToLockedContainer();
338
339  // Suspend/resume cycle causes wallpaper to refresh, loading a new desktop
340  // background that will animate in on top of the old one.
341  controller->CreateEmptyWallpaper();
342
343  // In this state we have two desktop background views stored in different
344  // properties. Both are in the lock screen background container.
345  RootWindowController* root_window_controller =
346      Shell::GetPrimaryRootWindowController();
347  EXPECT_TRUE(root_window_controller->animating_wallpaper_controller()->
348              GetController(false));
349  EXPECT_TRUE(root_window_controller->wallpaper_controller());
350  EXPECT_EQ(0, ChildCountForContainer(kDesktopBackgroundId));
351  EXPECT_EQ(2, ChildCountForContainer(kLockScreenBackgroundId));
352
353  // Before the wallpaper's animation completes, user unlocks the screen, which
354  // moves the desktop to the back.
355  controller->MoveDesktopToUnlockedContainer();
356
357  // Ensure both desktop backgrounds have moved.
358  EXPECT_EQ(2, ChildCountForContainer(kDesktopBackgroundId));
359  EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
360
361  // Finish the new desktop background animation.
362  RunDesktopControllerAnimation();
363
364  // Now there is one desktop background, in the back.
365  EXPECT_EQ(1, ChildCountForContainer(kDesktopBackgroundId));
366  EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
367}
368
369// Test for crbug.com/156542. Animating wallpaper should immediately finish
370// animation and replace current wallpaper before next animation starts.
371TEST_F(DesktopBackgroundControllerTest, ChangeWallpaperQuick) {
372  // We cannot short-circuit animations for this test.
373  ui::ScopedAnimationDurationScaleMode normal_duration_mode(
374      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
375
376  // Reset wallpaper state, see ControllerOwnership above.
377  DesktopBackgroundController* controller =
378      Shell::GetInstance()->desktop_background_controller();
379  controller->CreateEmptyWallpaper();
380
381  // Run wallpaper show animation to completion.
382  RunDesktopControllerAnimation();
383
384  // Change to a new wallpaper.
385  controller->CreateEmptyWallpaper();
386
387  RootWindowController* root_window_controller =
388      Shell::GetPrimaryRootWindowController();
389  DesktopBackgroundWidgetController* animating_controller =
390      root_window_controller->animating_wallpaper_controller()->
391      GetController(false);
392  EXPECT_TRUE(animating_controller);
393  EXPECT_TRUE(root_window_controller->wallpaper_controller());
394
395  // Change to another wallpaper before animation finished.
396  controller->CreateEmptyWallpaper();
397
398  // The animating controller should immediately move to desktop controller.
399  EXPECT_EQ(animating_controller,
400            root_window_controller->wallpaper_controller());
401
402  // Cache the new animating controller.
403  animating_controller = root_window_controller->
404      animating_wallpaper_controller()->GetController(false);
405
406  // Run wallpaper show animation to completion.
407  ASSERT_NO_FATAL_FAILURE(
408      RunAnimationForWidget(
409          root_window_controller->animating_wallpaper_controller()->
410              GetController(false)->widget()));
411
412  EXPECT_TRUE(root_window_controller->wallpaper_controller());
413  EXPECT_FALSE(root_window_controller->animating_wallpaper_controller()->
414               GetController(false));
415  // The desktop controller should be the last created animating controller.
416  EXPECT_EQ(animating_controller,
417            root_window_controller->wallpaper_controller());
418}
419
420TEST_F(DesktopBackgroundControllerTest, GetAppropriateResolution) {
421  // TODO(derat|oshima|bshe): Configuring desktops seems busted on Win8,
422  // even when just a single display is being used -- the small wallpaper
423  // is used instead of the large one. Track down the cause of the problem
424  // and only use a SupportsMultipleDisplays() clause for the dual-display
425  // code below.
426  if (!SupportsMultipleDisplays())
427    return;
428
429  test::DisplayManagerTestApi display_manager_test_api(
430      Shell::GetInstance()->display_manager());
431
432  // Small wallpaper images should be used for configurations less than or
433  // equal to kSmallWallpaperMaxWidth by kSmallWallpaperMaxHeight, even if
434  // multiple displays are connected.
435  display_manager_test_api.UpdateDisplay("800x600");
436  EXPECT_EQ(WALLPAPER_RESOLUTION_SMALL,
437            controller_->GetAppropriateResolution());
438  display_manager_test_api.UpdateDisplay("800x600,800x600");
439  EXPECT_EQ(WALLPAPER_RESOLUTION_SMALL,
440            controller_->GetAppropriateResolution());
441  display_manager_test_api.UpdateDisplay("1366x800");
442  EXPECT_EQ(WALLPAPER_RESOLUTION_SMALL,
443            controller_->GetAppropriateResolution());
444
445  // At larger sizes, large wallpapers should be used.
446  display_manager_test_api.UpdateDisplay("1367x800");
447  EXPECT_EQ(WALLPAPER_RESOLUTION_LARGE,
448            controller_->GetAppropriateResolution());
449  display_manager_test_api.UpdateDisplay("1367x801");
450  EXPECT_EQ(WALLPAPER_RESOLUTION_LARGE,
451            controller_->GetAppropriateResolution());
452  display_manager_test_api.UpdateDisplay("2560x1700");
453  EXPECT_EQ(WALLPAPER_RESOLUTION_LARGE,
454            controller_->GetAppropriateResolution());
455}
456
457// Test that DesktopBackgroundController loads the appropriate wallpaper
458// images as specified via command-line flags in various situations.
459// Splitting these into separate tests avoids needing to run animations.
460// TODO(derat): Combine these into a single test -- see
461// RunDesktopControllerAnimation()'s TODO.
462TEST_F(DesktopBackgroundControllerTest, SmallDefaultWallpaper) {
463  if (!SupportsMultipleDisplays())
464    return;
465
466  WriteWallpapersAndSetFlags();
467  TestObserver observer(controller_);
468
469  // At 800x600, the small wallpaper should be loaded.
470  test::DisplayManagerTestApi display_manager_test_api(
471      Shell::GetInstance()->display_manager());
472  display_manager_test_api.UpdateDisplay("800x600");
473  ASSERT_TRUE(controller_->SetDefaultWallpaper(false));
474  observer.WaitForWallpaperDataChanged();
475  EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
476                               kSmallWallpaperColor));
477
478  // Requesting the same wallpaper again should be a no-op.
479  ASSERT_FALSE(controller_->SetDefaultWallpaper(false));
480}
481
482TEST_F(DesktopBackgroundControllerTest, LargeDefaultWallpaper) {
483  if (!SupportsMultipleDisplays())
484    return;
485
486  WriteWallpapersAndSetFlags();
487  TestObserver observer(controller_);
488  test::DisplayManagerTestApi display_manager_test_api(
489      Shell::GetInstance()->display_manager());
490  display_manager_test_api.UpdateDisplay("1600x1200");
491  ASSERT_TRUE(controller_->SetDefaultWallpaper(false));
492  observer.WaitForWallpaperDataChanged();
493  EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
494                               kLargeWallpaperColor));
495}
496
497TEST_F(DesktopBackgroundControllerTest, SmallGuestWallpaper) {
498  if (!SupportsMultipleDisplays())
499    return;
500
501  WriteWallpapersAndSetFlags();
502  TestObserver observer(controller_);
503  test::DisplayManagerTestApi display_manager_test_api(
504      Shell::GetInstance()->display_manager());
505  display_manager_test_api.UpdateDisplay("800x600");
506  ASSERT_TRUE(controller_->SetDefaultWallpaper(true));
507  observer.WaitForWallpaperDataChanged();
508  EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
509                               kSmallGuestWallpaperColor));
510}
511
512TEST_F(DesktopBackgroundControllerTest, LargeGuestWallpaper) {
513  if (!SupportsMultipleDisplays())
514    return;
515
516  WriteWallpapersAndSetFlags();
517  TestObserver observer(controller_);
518  test::DisplayManagerTestApi display_manager_test_api(
519      Shell::GetInstance()->display_manager());
520  display_manager_test_api.UpdateDisplay("1600x1200");
521  ASSERT_TRUE(controller_->SetDefaultWallpaper(true));
522  observer.WaitForWallpaperDataChanged();
523  EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
524                               kLargeGuestWallpaperColor));
525}
526
527}  // namespace internal
528}  // namespace ash
529