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 35 using ash::internal::DesktopBackgroundWidgetController; 36 using content::BrowserThread; 37 38 namespace ash { 39 namespace { 40 41 const SkColor kTransparentColor = SkColorSetARGB(0x00, 0x00, 0x00, 0x00); 42 43 internal::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. 50 gfx::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 67 const int kSmallWallpaperMaxWidth = 1366; 68 const int kSmallWallpaperMaxHeight = 800; 69 const int kLargeWallpaperMaxWidth = 2560; 70 const int kLargeWallpaperMaxHeight = 1700; 71 const int kWallpaperThumbnailWidth = 108; 72 const int kWallpaperThumbnailHeight = 68; 73 74 // DesktopBackgroundController::WallpaperLoader wraps background wallpaper 75 // loading. 76 class 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 174 DesktopBackgroundController::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 183 DesktopBackgroundController::~DesktopBackgroundController() { 184 CancelPendingWallpaperOperation(); 185 } 186 187 gfx::ImageSkia DesktopBackgroundController::GetWallpaper() const { 188 if (current_wallpaper_) 189 return current_wallpaper_->wallpaper_image(); 190 return gfx::ImageSkia(); 191 } 192 193 void DesktopBackgroundController::AddObserver( 194 DesktopBackgroundControllerObserver* observer) { 195 observers_.AddObserver(observer); 196 } 197 198 void DesktopBackgroundController::RemoveObserver( 199 DesktopBackgroundControllerObserver* observer) { 200 observers_.RemoveObserver(observer); 201 } 202 203 WallpaperLayout DesktopBackgroundController::GetWallpaperLayout() const { 204 if (current_wallpaper_) 205 return current_wallpaper_->layout(); 206 return WALLPAPER_LAYOUT_CENTER_CROPPED; 207 } 208 209 void 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 236 bool 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 272 void 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 291 void 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 300 void DesktopBackgroundController::SetDesktopBackgroundSolidColorMode( 301 SkColor color) { 302 background_color_ = color; 303 desktop_background_mode_ = BACKGROUND_SOLID_COLOR; 304 305 InstallDesktopControllerForAllWindows(); 306 } 307 308 void DesktopBackgroundController::CreateEmptyWallpaper() { 309 current_wallpaper_.reset(NULL); 310 SetDesktopBackgroundImageMode(); 311 } 312 313 WallpaperResolution 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 330 bool DesktopBackgroundController::MoveDesktopToLockedContainer() { 331 if (locked_) 332 return false; 333 locked_ = true; 334 return ReparentBackgroundWidgets(GetBackgroundContainerId(false), 335 GetBackgroundContainerId(true)); 336 } 337 338 bool DesktopBackgroundController::MoveDesktopToUnlockedContainer() { 339 if (!locked_) 340 return false; 341 locked_ = false; 342 return ReparentBackgroundWidgets(GetBackgroundContainerId(true), 343 GetBackgroundContainerId(false)); 344 } 345 346 bool 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 356 bool DesktopBackgroundController::CustomWallpaperIsAlreadyLoaded( 357 const gfx::ImageSkia& image) const { 358 return current_wallpaper_.get() && 359 current_wallpaper_->wallpaper_image().BackedBySameObjectAs(image); 360 } 361 362 void DesktopBackgroundController::SetDesktopBackgroundImageMode() { 363 desktop_background_mode_ = BACKGROUND_IMAGE; 364 InstallDesktopControllerForAllWindows(); 365 } 366 367 void 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 382 ui::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 394 void 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 423 void 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 431 bool 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 469 int DesktopBackgroundController::GetBackgroundContainerId(bool locked) { 470 return locked ? internal::kShellWindowId_LockScreenBackgroundContainer : 471 internal::kShellWindowId_DesktopBackgroundContainer; 472 } 473 474 } // namespace ash 475