1 // Copyright (c) 2013 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 "chrome/browser/chromeos/login/wallpaper_manager.h" 6 7 #include <vector> 8 9 #include "ash/shell.h" 10 #include "base/command_line.h" 11 #include "base/debug/trace_event.h" 12 #include "base/file_util.h" 13 #include "base/files/file_enumerator.h" 14 #include "base/files/file_path.h" 15 #include "base/logging.h" 16 #include "base/metrics/histogram.h" 17 #include "base/path_service.h" 18 #include "base/prefs/pref_registry_simple.h" 19 #include "base/prefs/pref_service.h" 20 #include "base/strings/string_number_conversions.h" 21 #include "base/strings/string_util.h" 22 #include "base/strings/stringprintf.h" 23 #include "base/threading/worker_pool.h" 24 #include "base/time/time.h" 25 #include "base/values.h" 26 #include "chrome/browser/browser_process.h" 27 #include "chrome/browser/chrome_notification_types.h" 28 #include "chrome/browser/chromeos/login/startup_utils.h" 29 #include "chrome/browser/chromeos/login/user.h" 30 #include "chrome/browser/chromeos/login/user_manager.h" 31 #include "chrome/browser/chromeos/login/wizard_controller.h" 32 #include "chrome/browser/chromeos/settings/cros_settings.h" 33 #include "chrome/browser/prefs/scoped_user_pref_update.h" 34 #include "chrome/common/chrome_paths.h" 35 #include "chrome/common/chrome_switches.h" 36 #include "chrome/common/pref_names.h" 37 #include "chromeos/chromeos_switches.h" 38 #include "chromeos/dbus/dbus_thread_manager.h" 39 #include "content/public/browser/browser_thread.h" 40 #include "content/public/browser/notification_service.h" 41 #include "ui/base/resource/resource_bundle.h" 42 #include "ui/gfx/codec/jpeg_codec.h" 43 #include "ui/gfx/image/image_skia_operations.h" 44 #include "ui/gfx/skia_util.h" 45 46 using content::BrowserThread; 47 48 namespace { 49 50 const int kWallpaperUpdateIntervalSec = 24 * 60 * 60; 51 52 // Default quality for encoding wallpaper. 53 const int kDefaultEncodingQuality = 90; 54 55 // A dictionary pref that maps usernames to file paths to their wallpapers. 56 // Deprecated. Will remove this const char after done migration. 57 const char kUserWallpapers[] = "UserWallpapers"; 58 59 const int kThumbnailWidth = 128; 60 const int kThumbnailHeight = 80; 61 62 const int kCacheWallpaperDelayMs = 500; 63 64 // A dictionary pref that maps usernames to wallpaper properties. 65 const char kUserWallpapersProperties[] = "UserWallpapersProperties"; 66 67 // Names of nodes with info about wallpaper in |kUserWallpapersProperties| 68 // dictionary. 69 const char kNewWallpaperDateNodeName[] = "date"; 70 const char kNewWallpaperLayoutNodeName[] = "layout"; 71 const char kNewWallpaperFileNodeName[] = "file"; 72 const char kNewWallpaperTypeNodeName[] = "type"; 73 74 // File path suffix of the original custom wallpaper. 75 const char kOriginalCustomWallpaperSuffix[] = "_wallpaper"; 76 77 // Maximum number of wallpapers cached by CacheUsersWallpapers(). 78 const int kMaxWallpapersToCache = 3; 79 80 // For our scaling ratios we need to round positive numbers. 81 int RoundPositive(double x) { 82 return static_cast<int>(floor(x + 0.5)); 83 } 84 85 // Returns custom wallpaper directory by appending |sub_dir| and |email| as sub 86 // directories. 87 base::FilePath GetCustomWallpaperDir(const char* sub_dir, 88 const std::string& email) { 89 base::FilePath custom_wallpaper_dir; 90 CHECK(PathService::Get(chrome::DIR_CHROMEOS_CUSTOM_WALLPAPERS, 91 &custom_wallpaper_dir)); 92 return custom_wallpaper_dir.Append(sub_dir).Append(email); 93 } 94 95 96 } // namespace 97 98 namespace chromeos { 99 100 const char kWallpaperSequenceTokenName[] = "wallpaper-sequence"; 101 102 const char kSmallWallpaperSuffix[] = "_small"; 103 const char kLargeWallpaperSuffix[] = "_large"; 104 105 const char kSmallWallpaperSubDir[] = "small"; 106 const char kLargeWallpaperSubDir[] = "large"; 107 const char kOriginalWallpaperSubDir[] = "original"; 108 const char kThumbnailWallpaperSubDir[] = "thumb"; 109 110 static WallpaperManager* g_wallpaper_manager = NULL; 111 112 // WallpaperManager, public: --------------------------------------------------- 113 114 // TestApi. For testing purpose 115 WallpaperManager::TestApi::TestApi(WallpaperManager* wallpaper_manager) 116 : wallpaper_manager_(wallpaper_manager) { 117 } 118 119 WallpaperManager::TestApi::~TestApi() { 120 } 121 122 base::FilePath WallpaperManager::TestApi::current_wallpaper_path() { 123 return wallpaper_manager_->current_wallpaper_path_; 124 } 125 126 // static 127 WallpaperManager* WallpaperManager::Get() { 128 if (!g_wallpaper_manager) 129 g_wallpaper_manager = new WallpaperManager(); 130 return g_wallpaper_manager; 131 } 132 133 WallpaperManager::WallpaperManager() 134 : no_observers_(true), 135 loaded_wallpapers_(0), 136 wallpaper_loader_(new UserImageLoader(ImageDecoder::ROBUST_JPEG_CODEC)), 137 command_line_for_testing_(NULL), 138 should_cache_wallpaper_(false), 139 weak_factory_(this) { 140 RestartTimer(); 141 registrar_.Add(this, 142 chrome::NOTIFICATION_LOGIN_USER_CHANGED, 143 content::NotificationService::AllSources()); 144 registrar_.Add(this, 145 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, 146 content::NotificationService::AllSources()); 147 registrar_.Add(this, 148 chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED, 149 content::NotificationService::AllSources()); 150 sequence_token_ = BrowserThread::GetBlockingPool()-> 151 GetNamedSequenceToken(kWallpaperSequenceTokenName); 152 task_runner_ = BrowserThread::GetBlockingPool()-> 153 GetSequencedTaskRunnerWithShutdownBehavior( 154 sequence_token_, 155 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 156 } 157 158 WallpaperManager::~WallpaperManager() { 159 // TODO(bshe): Lifetime of WallpaperManager needs more consideration. 160 // http://crbug.com/171694 161 DCHECK(no_observers_); 162 ClearObsoleteWallpaperPrefs(); 163 weak_factory_.InvalidateWeakPtrs(); 164 } 165 166 void WallpaperManager::Shutdown() { 167 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); 168 system::TimezoneSettings::GetInstance()->RemoveObserver(this); 169 CrosSettings::Get()->RemoveSettingsObserver( 170 kAccountsPrefShowUserNamesOnSignIn, this); 171 no_observers_ = true; 172 } 173 174 // static 175 void WallpaperManager::RegisterPrefs(PrefRegistrySimple* registry) { 176 registry->RegisterDictionaryPref(prefs::kUsersWallpaperInfo); 177 registry->RegisterDictionaryPref(kUserWallpapers); 178 registry->RegisterDictionaryPref(kUserWallpapersProperties); 179 } 180 181 void WallpaperManager::AddObservers() { 182 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); 183 system::TimezoneSettings::GetInstance()->AddObserver(this); 184 CrosSettings::Get()->AddSettingsObserver(kAccountsPrefShowUserNamesOnSignIn, 185 this); 186 no_observers_ = false; 187 } 188 189 void WallpaperManager::EnsureLoggedInUserWallpaperLoaded() { 190 // Some browser tests do not have a shell instance. As no wallpaper is needed 191 // in these tests anyway, avoid loading one, preventing crashes and speeding 192 // up the tests. 193 if (!ash::Shell::HasInstance()) 194 return; 195 196 WallpaperInfo info; 197 if (GetLoggedInUserWallpaperInfo(&info)) { 198 // TODO(sschmitz): We need an index for default wallpapers for the new UI. 199 RecordUma(info.type, -1); 200 if (info == current_user_wallpaper_info_) 201 return; 202 } 203 SetUserWallpaper(UserManager::Get()->GetLoggedInUser()->email()); 204 } 205 206 void WallpaperManager::ClearWallpaperCache() { 207 // Cancel callback for previous cache requests. 208 weak_factory_.InvalidateWeakPtrs(); 209 wallpaper_cache_.clear(); 210 } 211 212 base::FilePath WallpaperManager::GetCustomWallpaperPath( 213 const char* sub_dir, 214 const std::string& email, 215 const std::string& file) { 216 base::FilePath custom_wallpaper_path = GetCustomWallpaperDir(sub_dir, email); 217 return custom_wallpaper_path.Append(file); 218 } 219 220 bool WallpaperManager::GetWallpaperFromCache(const std::string& email, 221 gfx::ImageSkia* wallpaper) { 222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 223 CustomWallpaperMap::const_iterator it = wallpaper_cache_.find(email); 224 if (it != wallpaper_cache_.end()) { 225 *wallpaper = (*it).second; 226 return true; 227 } 228 return false; 229 } 230 231 base::FilePath WallpaperManager::GetOriginalWallpaperPathForUser( 232 const std::string& username) { 233 std::string filename = username + kOriginalCustomWallpaperSuffix; 234 base::FilePath user_data_dir; 235 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 236 return user_data_dir.AppendASCII(filename); 237 } 238 239 base::FilePath WallpaperManager::GetWallpaperPathForUser( 240 const std::string& username, 241 bool is_small) { 242 const char* suffix = is_small ? 243 kSmallWallpaperSuffix : kLargeWallpaperSuffix; 244 245 std::string filename = base::StringPrintf("%s_wallpaper%s", 246 username.c_str(), 247 suffix); 248 base::FilePath user_data_dir; 249 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 250 return user_data_dir.AppendASCII(filename); 251 } 252 253 bool WallpaperManager::GetLoggedInUserWallpaperInfo(WallpaperInfo* info) { 254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 255 256 if (UserManager::Get()->IsLoggedInAsStub()) { 257 info->file = current_user_wallpaper_info_.file = ""; 258 info->layout = current_user_wallpaper_info_.layout = 259 ash::WALLPAPER_LAYOUT_CENTER_CROPPED; 260 info->type = current_user_wallpaper_info_.type = User::DEFAULT; 261 return true; 262 } 263 264 return GetUserWallpaperInfo(UserManager::Get()->GetLoggedInUser()->email(), 265 info); 266 } 267 268 void WallpaperManager::InitializeWallpaper() { 269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 270 UserManager* user_manager = UserManager::Get(); 271 272 CommandLine* command_line = GetComandLine(); 273 if (command_line->HasSwitch(chromeos::switches::kGuestSession)) { 274 // Guest wallpaper should be initialized when guest login. 275 // Note: This maybe called before login. So IsLoggedInAsGuest can not be 276 // used here to determine if current user is guest. 277 return; 278 } 279 280 if (command_line->HasSwitch(::switches::kTestType)) 281 WizardController::SetZeroDelays(); 282 283 // Zero delays is also set in autotests. 284 if (WizardController::IsZeroDelayEnabled()) { 285 // Ensure tests have some sort of wallpaper. 286 ash::Shell::GetInstance()->desktop_background_controller()-> 287 CreateEmptyWallpaper(); 288 return; 289 } 290 291 if (!user_manager->IsUserLoggedIn()) { 292 if (!StartupUtils::IsDeviceRegistered()) 293 SetDefaultWallpaper(); 294 else 295 InitializeRegisteredDeviceWallpaper(); 296 return; 297 } 298 SetUserWallpaper(user_manager->GetLoggedInUser()->email()); 299 } 300 301 void WallpaperManager::Observe(int type, 302 const content::NotificationSource& source, 303 const content::NotificationDetails& details) { 304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 305 switch (type) { 306 case chrome::NOTIFICATION_LOGIN_USER_CHANGED: { 307 ClearWallpaperCache(); 308 break; 309 } 310 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: { 311 if (!GetComandLine()->HasSwitch(switches::kDisableBootAnimation)) { 312 BrowserThread::PostDelayedTask( 313 BrowserThread::UI, FROM_HERE, 314 base::Bind(&WallpaperManager::CacheUsersWallpapers, 315 weak_factory_.GetWeakPtr()), 316 base::TimeDelta::FromMilliseconds(kCacheWallpaperDelayMs)); 317 } else { 318 should_cache_wallpaper_ = true; 319 } 320 break; 321 } 322 case chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED: { 323 if (should_cache_wallpaper_) { 324 BrowserThread::PostDelayedTask( 325 BrowserThread::UI, FROM_HERE, 326 base::Bind(&WallpaperManager::CacheUsersWallpapers, 327 weak_factory_.GetWeakPtr()), 328 base::TimeDelta::FromMilliseconds(kCacheWallpaperDelayMs)); 329 should_cache_wallpaper_ = false; 330 } 331 break; 332 } 333 case chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED: { 334 if (*content::Details<const std::string>(details).ptr() == 335 kAccountsPrefShowUserNamesOnSignIn) { 336 InitializeRegisteredDeviceWallpaper(); 337 } 338 break; 339 } 340 default: 341 NOTREACHED() << "Unexpected notification " << type; 342 } 343 } 344 345 void WallpaperManager::RemoveUserWallpaperInfo(const std::string& email) { 346 PrefService* prefs = g_browser_process->local_state(); 347 DictionaryPrefUpdate prefs_wallpapers_info_update(prefs, 348 prefs::kUsersWallpaperInfo); 349 prefs_wallpapers_info_update->RemoveWithoutPathExpansion(email, NULL); 350 351 DeleteUserWallpapers(email); 352 } 353 354 bool WallpaperManager::ResizeWallpaper( 355 const UserImage& wallpaper, 356 ash::WallpaperLayout layout, 357 int preferred_width, 358 int preferred_height, 359 scoped_refptr<base::RefCountedBytes>* output) { 360 DCHECK(BrowserThread::GetBlockingPool()-> 361 IsRunningSequenceOnCurrentThread(sequence_token_)); 362 int width = wallpaper.image().width(); 363 int height = wallpaper.image().height(); 364 int resized_width; 365 int resized_height; 366 *output = new base::RefCountedBytes(); 367 368 if (layout == ash::WALLPAPER_LAYOUT_CENTER_CROPPED) { 369 // Do not resize custom wallpaper if it is smaller than preferred size. 370 if (!(width > preferred_width && height > preferred_height)) 371 return false; 372 373 double horizontal_ratio = static_cast<double>(preferred_width) / width; 374 double vertical_ratio = static_cast<double>(preferred_height) / height; 375 if (vertical_ratio > horizontal_ratio) { 376 resized_width = 377 RoundPositive(static_cast<double>(width) * vertical_ratio); 378 resized_height = preferred_height; 379 } else { 380 resized_width = preferred_width; 381 resized_height = 382 RoundPositive(static_cast<double>(height) * horizontal_ratio); 383 } 384 } else if (layout == ash::WALLPAPER_LAYOUT_STRETCH) { 385 resized_width = preferred_width; 386 resized_height = preferred_height; 387 } else { 388 resized_width = width; 389 resized_height = height; 390 } 391 392 gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage( 393 wallpaper.image(), 394 skia::ImageOperations::RESIZE_LANCZOS3, 395 gfx::Size(resized_width, resized_height)); 396 397 SkBitmap image = *(resized_image.bitmap()); 398 SkAutoLockPixels lock_input(image); 399 gfx::JPEGCodec::Encode( 400 reinterpret_cast<unsigned char*>(image.getAddr32(0, 0)), 401 gfx::JPEGCodec::FORMAT_SkBitmap, 402 image.width(), 403 image.height(), 404 image.width() * image.bytesPerPixel(), 405 kDefaultEncodingQuality, &(*output)->data()); 406 return true; 407 } 408 409 void WallpaperManager::ResizeAndSaveWallpaper(const UserImage& wallpaper, 410 const base::FilePath& path, 411 ash::WallpaperLayout layout, 412 int preferred_width, 413 int preferred_height) { 414 if (layout == ash::WALLPAPER_LAYOUT_CENTER) { 415 // TODO(bshe): Generates cropped custom wallpaper for CENTER layout. 416 if (base::PathExists(path)) 417 base::DeleteFile(path, false); 418 return; 419 } 420 scoped_refptr<base::RefCountedBytes> data; 421 if (ResizeWallpaper(wallpaper, layout, preferred_width, preferred_height, 422 &data)) { 423 SaveWallpaperInternal(path, 424 reinterpret_cast<const char*>(data->front()), 425 data->size()); 426 } 427 } 428 429 void WallpaperManager::RestartTimer() { 430 timer_.Stop(); 431 // Determine the next update time as the earliest local midnight after now. 432 // Note that this may be more than kWallpaperUpdateIntervalSec seconds in the 433 // future due to DST. 434 base::Time now = base::Time::Now(); 435 base::TimeDelta update_interval = base::TimeDelta::FromSeconds( 436 kWallpaperUpdateIntervalSec); 437 base::Time future = now + update_interval; 438 base::Time next_update = future.LocalMidnight(); 439 while (next_update < now) { 440 future += update_interval; 441 next_update = future.LocalMidnight(); 442 } 443 int64 remaining_seconds = (next_update - now).InSeconds(); 444 DCHECK(remaining_seconds > 0); 445 // Set up a one shot timer which will batch update wallpaper at midnight. 446 timer_.Start(FROM_HERE, 447 base::TimeDelta::FromSeconds(remaining_seconds), 448 this, 449 &WallpaperManager::BatchUpdateWallpaper); 450 } 451 452 void WallpaperManager::SetCustomWallpaper(const std::string& username, 453 const std::string& file, 454 ash::WallpaperLayout layout, 455 User::WallpaperType type, 456 const UserImage& wallpaper) { 457 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 458 459 base::FilePath wallpaper_path = GetCustomWallpaperPath( 460 kOriginalWallpaperSubDir, 461 username, 462 file); 463 464 // If decoded wallpaper is empty, we are probably failed to decode the file. 465 // Use default wallpaper in this case. 466 if (wallpaper.image().isNull()) { 467 SetDefaultWallpaper(); 468 return; 469 } 470 471 bool is_persistent = 472 !UserManager::Get()->IsUserNonCryptohomeDataEphemeral(username); 473 474 wallpaper.image().EnsureRepsForSupportedScaleFactors(); 475 scoped_ptr<gfx::ImageSkia> deep_copy(wallpaper.image().DeepCopy()); 476 477 WallpaperInfo wallpaper_info = { 478 wallpaper_path.value(), 479 layout, 480 type, 481 // Date field is not used. 482 base::Time::Now().LocalMidnight() 483 }; 484 // Block shutdown on this task. Otherwise, we may lost the custom wallpaper 485 // user selected. 486 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner = 487 BrowserThread::GetBlockingPool()-> 488 GetSequencedTaskRunnerWithShutdownBehavior(sequence_token_, 489 base::SequencedWorkerPool::BLOCK_SHUTDOWN); 490 // TODO(bshe): This may break if RawImage becomes RefCountedMemory. 491 blocking_task_runner->PostTask(FROM_HERE, 492 base::Bind(&WallpaperManager::ProcessCustomWallpaper, 493 base::Unretained(this), 494 username, 495 is_persistent, 496 wallpaper_info, 497 base::Passed(&deep_copy), 498 wallpaper.raw_image())); 499 ash::Shell::GetInstance()->desktop_background_controller()-> 500 SetCustomWallpaper(wallpaper.image(), layout); 501 502 // User's custom wallpaper path is determined by username/email and the 503 // appropriate wallpaper resolution in GetCustomWallpaperInternal. 504 WallpaperInfo info = { 505 file, 506 layout, 507 User::CUSTOMIZED, 508 base::Time::Now().LocalMidnight() 509 }; 510 SetUserWallpaperInfo(username, info, is_persistent); 511 } 512 513 void WallpaperManager::SetDefaultWallpaper() { 514 current_wallpaper_path_.clear(); 515 if (ash::Shell::GetInstance()->desktop_background_controller()-> 516 SetDefaultWallpaper(UserManager::Get()->IsLoggedInAsGuest())) 517 loaded_wallpapers_++; 518 } 519 520 void WallpaperManager::SetInitialUserWallpaper(const std::string& username, 521 bool is_persistent) { 522 current_user_wallpaper_info_.file = ""; 523 current_user_wallpaper_info_.layout = ash::WALLPAPER_LAYOUT_CENTER_CROPPED; 524 current_user_wallpaper_info_.type = User::DEFAULT; 525 current_user_wallpaper_info_.date = base::Time::Now().LocalMidnight(); 526 527 WallpaperInfo info = current_user_wallpaper_info_; 528 SetUserWallpaperInfo(username, info, is_persistent); 529 SetLastSelectedUser(username); 530 531 // Some browser tests do not have a shell instance. As no wallpaper is needed 532 // in these tests anyway, avoid loading one, preventing crashes and speeding 533 // up the tests. 534 if (ash::Shell::HasInstance()) 535 SetDefaultWallpaper(); 536 } 537 538 void WallpaperManager::SetUserWallpaperInfo(const std::string& username, 539 const WallpaperInfo& info, 540 bool is_persistent) { 541 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 542 current_user_wallpaper_info_ = info; 543 if (!is_persistent) 544 return; 545 546 PrefService* local_state = g_browser_process->local_state(); 547 DictionaryPrefUpdate wallpaper_update(local_state, 548 prefs::kUsersWallpaperInfo); 549 550 base::DictionaryValue* wallpaper_info_dict = new base::DictionaryValue(); 551 wallpaper_info_dict->SetString(kNewWallpaperDateNodeName, 552 base::Int64ToString(info.date.ToInternalValue())); 553 wallpaper_info_dict->SetString(kNewWallpaperFileNodeName, info.file); 554 wallpaper_info_dict->SetInteger(kNewWallpaperLayoutNodeName, info.layout); 555 wallpaper_info_dict->SetInteger(kNewWallpaperTypeNodeName, info.type); 556 wallpaper_update->SetWithoutPathExpansion(username, wallpaper_info_dict); 557 } 558 559 void WallpaperManager::SetLastSelectedUser( 560 const std::string& last_selected_user) { 561 last_selected_user_ = last_selected_user; 562 } 563 564 void WallpaperManager::SetUserWallpaper(const std::string& email) { 565 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 566 if (email == UserManager::kGuestUserName) { 567 SetDefaultWallpaper(); 568 return; 569 } 570 571 if (!UserManager::Get()->IsKnownUser(email)) 572 return; 573 574 SetLastSelectedUser(email); 575 576 WallpaperInfo info; 577 578 if (GetUserWallpaperInfo(email, &info)) { 579 gfx::ImageSkia user_wallpaper; 580 if (GetWallpaperFromCache(email, &user_wallpaper)) { 581 ash::Shell::GetInstance()->desktop_background_controller()-> 582 SetCustomWallpaper(user_wallpaper, info.layout); 583 } else { 584 if (info.type == User::CUSTOMIZED) { 585 ash::WallpaperResolution resolution = ash::Shell::GetInstance()-> 586 desktop_background_controller()->GetAppropriateResolution(); 587 const char* sub_dir = (resolution == ash::WALLPAPER_RESOLUTION_SMALL) ? 588 kSmallWallpaperSubDir : kLargeWallpaperSubDir; 589 // Wallpaper is not resized when layout is ash::WALLPAPER_LAYOUT_CENTER. 590 // Original wallpaper should be used in this case. 591 // TODO(bshe): Generates cropped custom wallpaper for CENTER layout. 592 if (info.layout == ash::WALLPAPER_LAYOUT_CENTER) 593 sub_dir = kOriginalWallpaperSubDir; 594 base::FilePath wallpaper_path = GetCustomWallpaperPath(sub_dir, email, 595 info.file); 596 if (current_wallpaper_path_ == wallpaper_path) 597 return; 598 current_wallpaper_path_ = wallpaper_path; 599 loaded_wallpapers_++; 600 601 task_runner_->PostTask(FROM_HERE, 602 base::Bind(&WallpaperManager::GetCustomWallpaperInternal, 603 base::Unretained(this), email, info, wallpaper_path, 604 true /* update wallpaper */)); 605 return; 606 } 607 608 if (info.file.empty()) { 609 // Uses default built-in wallpaper when file is empty. Eventually, we 610 // will only ship one built-in wallpaper in ChromeOS image. 611 SetDefaultWallpaper(); 612 return; 613 } 614 615 // Load downloaded ONLINE or converted DEFAULT wallpapers. 616 LoadWallpaper(email, info, true /* update wallpaper */); 617 } 618 } else { 619 SetInitialUserWallpaper(email, true); 620 } 621 } 622 623 void WallpaperManager::SetWallpaperFromImageSkia( 624 const gfx::ImageSkia& wallpaper, 625 ash::WallpaperLayout layout) { 626 ash::Shell::GetInstance()->desktop_background_controller()-> 627 SetCustomWallpaper(wallpaper, layout); 628 } 629 630 void WallpaperManager::UpdateWallpaper() { 631 ClearWallpaperCache(); 632 current_wallpaper_path_.clear(); 633 // For GAIA login flow, the last_selected_user_ may not be set before user 634 // login. If UpdateWallpaper is called at GAIA login screen, no wallpaper will 635 // be set. It could result a black screen on external monitors. 636 // See http://crbug.com/265689 for detail. 637 if (last_selected_user_.empty()) { 638 SetDefaultWallpaper(); 639 return; 640 } 641 SetUserWallpaper(last_selected_user_); 642 } 643 644 // WallpaperManager, private: -------------------------------------------------- 645 646 void WallpaperManager::BatchUpdateWallpaper() { 647 NOTIMPLEMENTED(); 648 } 649 650 void WallpaperManager::CacheUsersWallpapers() { 651 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 652 UserList users = UserManager::Get()->GetUsers(); 653 654 if (!users.empty()) { 655 UserList::const_iterator it = users.begin(); 656 // Skip the wallpaper of first user in the list. It should have been cached. 657 it++; 658 for (int cached = 0; 659 it != users.end() && cached < kMaxWallpapersToCache; 660 ++it, ++cached) { 661 std::string user_email = (*it)->email(); 662 CacheUserWallpaper(user_email); 663 } 664 } 665 } 666 667 void WallpaperManager::CacheUserWallpaper(const std::string& email) { 668 if (wallpaper_cache_.find(email) == wallpaper_cache_.end()) 669 return; 670 WallpaperInfo info; 671 if (GetUserWallpaperInfo(email, &info)) { 672 base::FilePath wallpaper_dir; 673 base::FilePath wallpaper_path; 674 if (info.type == User::CUSTOMIZED) { 675 ash::WallpaperResolution resolution = ash::Shell::GetInstance()-> 676 desktop_background_controller()->GetAppropriateResolution(); 677 const char* sub_dir = (resolution == ash::WALLPAPER_RESOLUTION_SMALL) ? 678 kSmallWallpaperSubDir : kLargeWallpaperSubDir; 679 base::FilePath wallpaper_path = GetCustomWallpaperPath(sub_dir, email, 680 info.file); 681 task_runner_->PostTask(FROM_HERE, 682 base::Bind(&WallpaperManager::GetCustomWallpaperInternal, 683 base::Unretained(this), email, info, wallpaper_path, 684 false /* do not update wallpaper */)); 685 return; 686 } 687 LoadWallpaper(email, info, false /* do not update wallpaper */); 688 } 689 } 690 691 void WallpaperManager::ClearObsoleteWallpaperPrefs() { 692 PrefService* prefs = g_browser_process->local_state(); 693 DictionaryPrefUpdate wallpaper_properties_pref(prefs, 694 kUserWallpapersProperties); 695 wallpaper_properties_pref->Clear(); 696 DictionaryPrefUpdate wallpapers_pref(prefs, kUserWallpapers); 697 wallpapers_pref->Clear(); 698 } 699 700 void WallpaperManager::DeleteAllExcept(const base::FilePath& path) { 701 base::FilePath dir = path.DirName(); 702 if (base::DirectoryExists(dir)) { 703 base::FileEnumerator files(dir, false, base::FileEnumerator::FILES); 704 for (base::FilePath current = files.Next(); !current.empty(); 705 current = files.Next()) { 706 if (current != path) 707 base::DeleteFile(current, false); 708 } 709 } 710 } 711 712 void WallpaperManager::DeleteWallpaperInList( 713 const std::vector<base::FilePath>& file_list) { 714 for (std::vector<base::FilePath>::const_iterator it = file_list.begin(); 715 it != file_list.end(); ++it) { 716 base::FilePath path = *it; 717 // Some users may still have legacy wallpapers with png extension. We need 718 // to delete these wallpapers too. 719 if (!base::DeleteFile(path, true) && 720 !base::DeleteFile(path.AddExtension(".png"), false)) { 721 LOG(ERROR) << "Failed to remove user wallpaper at " << path.value(); 722 } 723 } 724 } 725 726 void WallpaperManager::DeleteUserWallpapers(const std::string& email) { 727 std::vector<base::FilePath> file_to_remove; 728 // Remove small user wallpaper. 729 base::FilePath wallpaper_path = GetWallpaperPathForUser(email, true); 730 file_to_remove.push_back(wallpaper_path); 731 wallpaper_path = GetCustomWallpaperDir(kSmallWallpaperSubDir, email); 732 file_to_remove.push_back(wallpaper_path); 733 734 // Remove large user wallpaper. 735 wallpaper_path = GetWallpaperPathForUser(email, false); 736 file_to_remove.push_back(wallpaper_path); 737 wallpaper_path = GetCustomWallpaperDir(kLargeWallpaperSubDir, email); 738 file_to_remove.push_back(wallpaper_path); 739 740 // Remove user wallpaper thumbnail. 741 wallpaper_path = GetCustomWallpaperDir(kThumbnailWallpaperSubDir, email); 742 file_to_remove.push_back(wallpaper_path); 743 744 // Remove original user wallpaper. 745 wallpaper_path = GetOriginalWallpaperPathForUser(email); 746 file_to_remove.push_back(wallpaper_path); 747 wallpaper_path = GetCustomWallpaperDir(kOriginalWallpaperSubDir, email); 748 file_to_remove.push_back(wallpaper_path); 749 750 base::WorkerPool::PostTask( 751 FROM_HERE, 752 base::Bind(&WallpaperManager::DeleteWallpaperInList, 753 base::Unretained(this), 754 file_to_remove), 755 false); 756 } 757 758 void WallpaperManager::EnsureCustomWallpaperDirectories( 759 const std::string& email) { 760 base::FilePath dir; 761 dir = GetCustomWallpaperDir(kSmallWallpaperSubDir, email); 762 if (!base::PathExists(dir)) 763 file_util::CreateDirectory(dir); 764 dir = GetCustomWallpaperDir(kLargeWallpaperSubDir, email); 765 if (!base::PathExists(dir)) 766 file_util::CreateDirectory(dir); 767 dir = GetCustomWallpaperDir(kOriginalWallpaperSubDir, email); 768 if (!base::PathExists(dir)) 769 file_util::CreateDirectory(dir); 770 dir = GetCustomWallpaperDir(kThumbnailWallpaperSubDir, email); 771 if (!base::PathExists(dir)) 772 file_util::CreateDirectory(dir); 773 } 774 775 CommandLine* WallpaperManager::GetComandLine() { 776 CommandLine* command_line = command_line_for_testing_ ? 777 command_line_for_testing_ : CommandLine::ForCurrentProcess(); 778 return command_line; 779 } 780 781 void WallpaperManager::FallbackToOldCustomWallpaper(const std::string& email, 782 const WallpaperInfo& info, 783 bool update_wallpaper){ 784 ash::WallpaperResolution resolution = ash::Shell::GetInstance()-> 785 desktop_background_controller()->GetAppropriateResolution(); 786 bool is_small = resolution == ash::WALLPAPER_RESOLUTION_SMALL; 787 base::FilePath wallpaper_path = GetWallpaperPathForUser(email, is_small); 788 789 task_runner_->PostTask(FROM_HERE, 790 base::Bind(&WallpaperManager::GetCustomWallpaperInternalOld, 791 base::Unretained(this), email, info, wallpaper_path, 792 true /* update wallpaper */)); 793 } 794 795 void WallpaperManager::InitializeRegisteredDeviceWallpaper() { 796 if (UserManager::Get()->IsUserLoggedIn()) 797 return; 798 799 bool disable_boot_animation = GetComandLine()-> 800 HasSwitch(switches::kDisableBootAnimation); 801 bool show_users = true; 802 bool result = CrosSettings::Get()->GetBoolean( 803 kAccountsPrefShowUserNamesOnSignIn, &show_users); 804 DCHECK(result) << "Unable to fetch setting " 805 << kAccountsPrefShowUserNamesOnSignIn; 806 const chromeos::UserList& users = UserManager::Get()->GetUsers(); 807 if (!show_users || users.empty()) { 808 // Boot into sign in form, preload default wallpaper. 809 SetDefaultWallpaper(); 810 return; 811 } 812 813 if (!disable_boot_animation) { 814 // Normal boot, load user wallpaper. 815 // If normal boot animation is disabled wallpaper would be set 816 // asynchronously once user pods are loaded. 817 SetUserWallpaper(users[0]->email()); 818 } 819 } 820 821 void WallpaperManager::LoadWallpaper(const std::string& email, 822 const WallpaperInfo& info, 823 bool update_wallpaper) { 824 base::FilePath wallpaper_dir; 825 base::FilePath wallpaper_path; 826 if (info.type == User::ONLINE) { 827 std::string file_name = GURL(info.file).ExtractFileName(); 828 ash::WallpaperResolution resolution = ash::Shell::GetInstance()-> 829 desktop_background_controller()->GetAppropriateResolution(); 830 // Only solid color wallpapers have stretch layout and they have only one 831 // resolution. 832 if (info.layout != ash::WALLPAPER_LAYOUT_STRETCH && 833 resolution == ash::WALLPAPER_RESOLUTION_SMALL) { 834 file_name = base::FilePath(file_name).InsertBeforeExtension( 835 kSmallWallpaperSuffix).value(); 836 } 837 CHECK(PathService::Get(chrome::DIR_CHROMEOS_WALLPAPERS, &wallpaper_dir)); 838 wallpaper_path = wallpaper_dir.Append(file_name); 839 if (current_wallpaper_path_ == wallpaper_path) 840 return; 841 if (update_wallpaper) 842 current_wallpaper_path_ = wallpaper_path; 843 loaded_wallpapers_++; 844 StartLoad(email, info, update_wallpaper, wallpaper_path); 845 } else if (info.type == User::DEFAULT) { 846 // Default wallpapers are migrated from M21 user profiles. A code refactor 847 // overlooked that case and caused these wallpapers not being loaded at all. 848 // On some slow devices, it caused login webui not visible after upgrade to 849 // M26 from M21. See crosbug.com/38429 for details. 850 base::FilePath user_data_dir; 851 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 852 wallpaper_path = user_data_dir.Append(info.file); 853 StartLoad(email, info, update_wallpaper, wallpaper_path); 854 } else { 855 // In unexpected cases, revert to default wallpaper to fail safely. See 856 // crosbug.com/38429. 857 LOG(ERROR) << "Wallpaper reverts to default unexpected."; 858 SetDefaultWallpaper(); 859 } 860 } 861 862 void WallpaperManager::MoveCustomWallpapers() { 863 UserList users = UserManager::Get()->GetUsers(); 864 if (!users.empty()) { 865 task_runner_->PostTask(FROM_HERE, 866 base::Bind(&WallpaperManager::MoveCustomWallpapersOnWorker, 867 base::Unretained(this), users)); 868 } 869 } 870 871 void WallpaperManager::MoveCustomWallpapersOnWorker(const UserList& users) { 872 DCHECK(BrowserThread::GetBlockingPool()-> 873 IsRunningSequenceOnCurrentThread(sequence_token_)); 874 875 base::FilePath from_path; 876 base::FilePath to_path; 877 // Move old custom wallpapers to new place for all existing users. 878 for (UserList::const_iterator it = users.begin(); it != users.end(); ++it) { 879 std::string email = (*it)->email(); 880 EnsureCustomWallpaperDirectories(email); 881 from_path = GetWallpaperPathForUser(email, true); 882 // Old wallpaper with extension name may still exist. 883 if (!base::PathExists(from_path)) 884 from_path = from_path.AddExtension(".png"); 885 if (base::PathExists(from_path)) { 886 // Appends DUMMY to the file name of moved custom wallpaper. This way we 887 // do not need to update WallpaperInfo for user. 888 to_path = GetCustomWallpaperPath(kSmallWallpaperSubDir, email, "DUMMY"); 889 base::Move(from_path, to_path); 890 } 891 from_path = GetWallpaperPathForUser(email, false); 892 if (!base::PathExists(from_path)) 893 from_path = from_path.AddExtension(".png"); 894 if (base::PathExists(from_path)) { 895 to_path = GetCustomWallpaperPath(kLargeWallpaperSubDir, email, "DUMMY"); 896 base::Move(from_path, to_path); 897 } 898 from_path = GetOriginalWallpaperPathForUser(email); 899 if (!base::PathExists(from_path)) 900 from_path = from_path.AddExtension(".png"); 901 if (base::PathExists(from_path)) { 902 to_path = GetCustomWallpaperPath(kOriginalWallpaperSubDir, email, 903 "DUMMY"); 904 base::Move(from_path, to_path); 905 } 906 } 907 } 908 909 bool WallpaperManager::GetUserWallpaperInfo(const std::string& email, 910 WallpaperInfo* info){ 911 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 912 913 if (UserManager::Get()->IsUserNonCryptohomeDataEphemeral(email)) { 914 // Default to the values cached in memory. 915 *info = current_user_wallpaper_info_; 916 917 // Ephemeral users do not save anything to local state. But we have got 918 // wallpaper info from memory. Returns true. 919 return true; 920 } 921 922 const DictionaryValue* user_wallpapers = g_browser_process->local_state()-> 923 GetDictionary(prefs::kUsersWallpaperInfo); 924 const base::DictionaryValue* wallpaper_info_dict; 925 if (user_wallpapers->GetDictionaryWithoutPathExpansion( 926 email, &wallpaper_info_dict)) { 927 info->file = ""; 928 info->layout = ash::WALLPAPER_LAYOUT_CENTER_CROPPED; 929 info->type = User::UNKNOWN; 930 info->date = base::Time::Now().LocalMidnight(); 931 wallpaper_info_dict->GetString(kNewWallpaperFileNodeName, &(info->file)); 932 int temp; 933 wallpaper_info_dict->GetInteger(kNewWallpaperLayoutNodeName, &temp); 934 info->layout = static_cast<ash::WallpaperLayout>(temp); 935 wallpaper_info_dict->GetInteger(kNewWallpaperTypeNodeName, &temp); 936 info->type = static_cast<User::WallpaperType>(temp); 937 std::string date_string; 938 int64 val; 939 if (!(wallpaper_info_dict->GetString(kNewWallpaperDateNodeName, 940 &date_string) && 941 base::StringToInt64(date_string, &val))) 942 val = 0; 943 info->date = base::Time::FromInternalValue(val); 944 return true; 945 } 946 947 return false; 948 } 949 950 void WallpaperManager::GetCustomWallpaperInternalOld( 951 const std::string& email, 952 const WallpaperInfo& info, 953 const base::FilePath& wallpaper_path, 954 bool update_wallpaper) { 955 DCHECK(BrowserThread::GetBlockingPool()-> 956 IsRunningSequenceOnCurrentThread(sequence_token_)); 957 std::string file_name = wallpaper_path.BaseName().value(); 958 959 if (!base::PathExists(wallpaper_path)) { 960 if (base::PathExists(wallpaper_path.AddExtension(".png"))) { 961 // Old wallpaper may have a png extension. 962 file_name += ".png"; 963 } else { 964 // Falls back on original file if the correct resoltuion file does not 965 // exist. This may happen when the original custom wallpaper is small or 966 // browser shutdown before resized wallpaper saved. 967 file_name = GetOriginalWallpaperPathForUser(email).BaseName().value(); 968 } 969 } 970 971 base::FilePath valid_path = wallpaper_path.DirName().Append(file_name); 972 if (!base::PathExists(valid_path)) 973 valid_path = valid_path.AddExtension(".png"); 974 BrowserThread::PostTask( 975 BrowserThread::UI, FROM_HERE, 976 base::Bind(&WallpaperManager::StartLoad, base::Unretained(this), email, 977 info, update_wallpaper, valid_path)); 978 BrowserThread::PostTask( 979 BrowserThread::UI, FROM_HERE, 980 base::Bind(&WallpaperManager::MoveCustomWallpapers, 981 base::Unretained(this))); 982 } 983 984 void WallpaperManager::GetCustomWallpaperInternal( 985 const std::string& email, 986 const WallpaperInfo& info, 987 const base::FilePath& wallpaper_path, 988 bool update_wallpaper) { 989 DCHECK(BrowserThread::GetBlockingPool()-> 990 IsRunningSequenceOnCurrentThread(sequence_token_)); 991 992 base::FilePath valid_path = wallpaper_path; 993 if (!base::PathExists(wallpaper_path)) { 994 // Falls back on original file if the correct resoltuion file does not 995 // exist. This may happen when the original custom wallpaper is small or 996 // browser shutdown before resized wallpaper saved. 997 valid_path = GetCustomWallpaperPath(kOriginalWallpaperSubDir, email, 998 info.file); 999 } 1000 1001 if (!base::PathExists(valid_path)) { 1002 BrowserThread::PostTask( 1003 BrowserThread::UI, 1004 FROM_HERE, 1005 base::Bind(&WallpaperManager::FallbackToOldCustomWallpaper, 1006 base::Unretained(this), 1007 email, 1008 info, 1009 update_wallpaper)); 1010 } else { 1011 BrowserThread::PostTask(BrowserThread::UI, 1012 FROM_HERE, 1013 base::Bind(&WallpaperManager::StartLoad, 1014 base::Unretained(this), 1015 email, 1016 info, 1017 update_wallpaper, 1018 valid_path)); 1019 } 1020 } 1021 1022 void WallpaperManager::OnWallpaperDecoded(const std::string& email, 1023 ash::WallpaperLayout layout, 1024 bool update_wallpaper, 1025 const UserImage& wallpaper) { 1026 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1027 TRACE_EVENT_ASYNC_END0("ui", "LoadAndDecodeWallpaper", this); 1028 1029 // If decoded wallpaper is empty, we are probably failed to decode the file. 1030 // Use default wallpaper in this case. 1031 if (wallpaper.image().isNull()) { 1032 // Updates user pref to default wallpaper. 1033 WallpaperInfo info = { 1034 "", 1035 ash::WALLPAPER_LAYOUT_CENTER_CROPPED, 1036 User::DEFAULT, 1037 base::Time::Now().LocalMidnight() 1038 }; 1039 SetUserWallpaperInfo(email, info, true); 1040 1041 if (update_wallpaper) 1042 SetDefaultWallpaper(); 1043 return; 1044 } 1045 1046 // Only cache user wallpaper at login screen. 1047 if (!UserManager::Get()->IsUserLoggedIn()) { 1048 wallpaper_cache_.insert(std::make_pair(email, wallpaper.image())); 1049 } 1050 if (update_wallpaper) { 1051 ash::Shell::GetInstance()->desktop_background_controller()-> 1052 SetCustomWallpaper(wallpaper.image(), layout); 1053 } 1054 } 1055 1056 void WallpaperManager::ProcessCustomWallpaper( 1057 const std::string& email, 1058 bool persistent, 1059 const WallpaperInfo& info, 1060 scoped_ptr<gfx::ImageSkia> image, 1061 const UserImage::RawImage& raw_image) { 1062 DCHECK(BrowserThread::GetBlockingPool()-> 1063 IsRunningSequenceOnCurrentThread(sequence_token_)); 1064 UserImage wallpaper(*image.get(), raw_image); 1065 if (persistent) { 1066 SaveCustomWallpaper(email, base::FilePath(info.file), info.layout, 1067 wallpaper); 1068 } 1069 } 1070 1071 void WallpaperManager::SaveCustomWallpaper(const std::string& email, 1072 const base::FilePath& original_path, 1073 ash::WallpaperLayout layout, 1074 const UserImage& wallpaper) { 1075 DCHECK(BrowserThread::GetBlockingPool()-> 1076 IsRunningSequenceOnCurrentThread(sequence_token_)); 1077 EnsureCustomWallpaperDirectories(email); 1078 std::string file_name = original_path.BaseName().value(); 1079 base::FilePath small_wallpaper_path = 1080 GetCustomWallpaperPath(kSmallWallpaperSubDir, email, file_name); 1081 base::FilePath large_wallpaper_path = 1082 GetCustomWallpaperPath(kLargeWallpaperSubDir, email, file_name); 1083 1084 std::vector<unsigned char> image_data = wallpaper.raw_image(); 1085 // Re-encode orginal file to jpeg format and saves the result in case that 1086 // resized wallpaper is not generated (i.e. chrome shutdown before resized 1087 // wallpaper is saved). 1088 ResizeAndSaveWallpaper(wallpaper, original_path, 1089 ash::WALLPAPER_LAYOUT_STRETCH, 1090 wallpaper.image().width(), 1091 wallpaper.image().height()); 1092 DeleteAllExcept(original_path); 1093 1094 ResizeAndSaveWallpaper(wallpaper, small_wallpaper_path, layout, 1095 ash::kSmallWallpaperMaxWidth, 1096 ash::kSmallWallpaperMaxHeight); 1097 DeleteAllExcept(small_wallpaper_path); 1098 ResizeAndSaveWallpaper(wallpaper, large_wallpaper_path, layout, 1099 ash::kLargeWallpaperMaxWidth, 1100 ash::kLargeWallpaperMaxHeight); 1101 DeleteAllExcept(large_wallpaper_path); 1102 } 1103 1104 void WallpaperManager::RecordUma(User::WallpaperType type, int index) { 1105 UMA_HISTOGRAM_ENUMERATION("Ash.Wallpaper.Type", type, 1106 User::WALLPAPER_TYPE_COUNT); 1107 } 1108 1109 void WallpaperManager::SaveWallpaperInternal(const base::FilePath& path, 1110 const char* data, 1111 int size) { 1112 int written_bytes = file_util::WriteFile(path, data, size); 1113 DCHECK(written_bytes == size); 1114 } 1115 1116 void WallpaperManager::StartLoad(const std::string& email, 1117 const WallpaperInfo& info, 1118 bool update_wallpaper, 1119 const base::FilePath& wallpaper_path) { 1120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1121 TRACE_EVENT_ASYNC_BEGIN0("ui", "LoadAndDecodeWallpaper", this); 1122 1123 // All wallpaper related operation should run on the same thread. So we pass 1124 // |sequence_token_| here. 1125 wallpaper_loader_->Start(wallpaper_path.value(), 0, sequence_token_, 1126 base::Bind(&WallpaperManager::OnWallpaperDecoded, 1127 base::Unretained(this), 1128 email, 1129 info.layout, 1130 update_wallpaper)); 1131 } 1132 1133 void WallpaperManager::SystemResumed(const base::TimeDelta& sleep_duration) { 1134 BatchUpdateWallpaper(); 1135 } 1136 1137 void WallpaperManager::TimezoneChanged(const icu::TimeZone& timezone) { 1138 RestartTimer(); 1139 } 1140 1141 } // namespace chromeos 1142