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