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