Home | History | Annotate | Download | only in login
      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