Home | History | Annotate | Download | only in chromeos
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/customization_document.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/bind_helpers.h"
     11 #include "base/file_util.h"
     12 #include "base/files/file_path.h"
     13 #include "base/json/json_reader.h"
     14 #include "base/logging.h"
     15 #include "base/memory/weak_ptr.h"
     16 #include "base/metrics/histogram.h"
     17 #include "base/path_service.h"
     18 #include "base/prefs/pref_registry_simple.h"
     19 #include "base/prefs/pref_service.h"
     20 #include "base/strings/string_split.h"
     21 #include "base/strings/string_util.h"
     22 #include "base/strings/stringprintf.h"
     23 #include "base/strings/utf_string_conversions.h"
     24 #include "base/time/time.h"
     25 #include "chrome/browser/browser_process.h"
     26 #include "chrome/browser/chromeos/customization_wallpaper_downloader.h"
     27 #include "chrome/browser/chromeos/extensions/default_app_order.h"
     28 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
     29 #include "chrome/browser/chromeos/login/wizard_controller.h"
     30 #include "chrome/browser/chromeos/net/delay_network_call.h"
     31 #include "chrome/browser/extensions/external_loader.h"
     32 #include "chrome/browser/extensions/external_provider_impl.h"
     33 #include "chrome/browser/profiles/profile.h"
     34 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
     35 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
     36 #include "chrome/common/chrome_paths.h"
     37 #include "chrome/common/extensions/extension_constants.h"
     38 #include "chrome/common/pref_names.h"
     39 #include "chromeos/system/statistics_provider.h"
     40 #include "components/pref_registry/pref_registry_syncable.h"
     41 #include "content/public/browser/browser_thread.h"
     42 #include "net/base/load_flags.h"
     43 #include "net/http/http_response_headers.h"
     44 #include "net/http/http_status_code.h"
     45 #include "net/url_request/url_fetcher.h"
     46 #include "ui/base/l10n/l10n_util.h"
     47 
     48 using content::BrowserThread;
     49 
     50 namespace chromeos {
     51 namespace {
     52 
     53   // Manifest attributes names.
     54 const char kVersionAttr[] = "version";
     55 const char kDefaultAttr[] = "default";
     56 const char kInitialLocaleAttr[] = "initial_locale";
     57 const char kInitialTimezoneAttr[] = "initial_timezone";
     58 const char kKeyboardLayoutAttr[] = "keyboard_layout";
     59 const char kHwidMapAttr[] = "hwid_map";
     60 const char kHwidMaskAttr[] = "hwid_mask";
     61 const char kSetupContentAttr[] = "setup_content";
     62 const char kEulaPageAttr[] = "eula_page";
     63 const char kDefaultWallpaperAttr[] = "default_wallpaper";
     64 const char kDefaultAppsAttr[] = "default_apps";
     65 const char kLocalizedContent[] = "localized_content";
     66 const char kDefaultAppsFolderName[] = "default_apps_folder_name";
     67 
     68 const char kAcceptedManifestVersion[] = "1.0";
     69 
     70 // Path to OEM partner startup customization manifest.
     71 const char kStartupCustomizationManifestPath[] =
     72     "/opt/oem/etc/startup_manifest.json";
     73 
     74 // This is subdirectory relative to PathService(DIR_CHROMEOS_CUSTOM_WALLPAPERS),
     75 // where downloaded (and resized) wallpaper is stored.
     76 const char kCustomizationDefaultWallpaperDir[] = "customization";
     77 
     78 // The original downloaded image file is stored under this name.
     79 const char kCustomizationDefaultWallpaperDownloadedFile[] =
     80     "default_downloaded_wallpaper.bin";
     81 
     82 // Name of local state option that tracks if services customization has been
     83 // applied.
     84 const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied";
     85 
     86 // Maximum number of retries to fetch file if network is not available.
     87 const int kMaxFetchRetries = 3;
     88 
     89 // Delay between file fetch retries if network is not available.
     90 const int kRetriesDelayInSec = 2;
     91 
     92 // Name of profile option that tracks cached version of service customization.
     93 const char kServicesCustomizationKey[] = "customization.manifest_cache";
     94 
     95 // Empty customization document that doesn't customize anything.
     96 const char kEmptyServicesCustomizationManifest[] = "{ \"version\": \"1.0\" }";
     97 
     98 // Global overrider for ServicesCustomizationDocument for tests.
     99 ServicesCustomizationDocument* g_test_services_customization_document = NULL;
    100 
    101 // Services customization document load results reported via the
    102 // "ServicesCustomization.LoadResult" histogram.
    103 // It is append-only enum due to use in a histogram!
    104 enum HistogramServicesCustomizationLoadResult {
    105   HISTOGRAM_LOAD_RESULT_SUCCESS = 0,
    106   HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND = 1,
    107   HISTOGRAM_LOAD_RESULT_PARSING_ERROR = 2,
    108   HISTOGRAM_LOAD_RESULT_RETRIES_FAIL = 3,
    109   HISTOGRAM_LOAD_RESULT_MAX_VALUE = 4
    110 };
    111 
    112 void LogManifestLoadResult(HistogramServicesCustomizationLoadResult result) {
    113   UMA_HISTOGRAM_ENUMERATION("ServicesCustomization.LoadResult",
    114                             result,
    115                             HISTOGRAM_LOAD_RESULT_MAX_VALUE);
    116 }
    117 
    118 std::string GetLocaleSpecificStringImpl(
    119     const base::DictionaryValue* root,
    120     const std::string& locale,
    121     const std::string& dictionary_name,
    122     const std::string& entry_name) {
    123   const base::DictionaryValue* dictionary_content = NULL;
    124   if (!root || !root->GetDictionary(dictionary_name, &dictionary_content))
    125     return std::string();
    126 
    127   const base::DictionaryValue* locale_dictionary = NULL;
    128   if (dictionary_content->GetDictionary(locale, &locale_dictionary)) {
    129     std::string result;
    130     if (locale_dictionary->GetString(entry_name, &result))
    131       return result;
    132   }
    133 
    134   const base::DictionaryValue* default_dictionary = NULL;
    135   if (dictionary_content->GetDictionary(kDefaultAttr, &default_dictionary)) {
    136     std::string result;
    137     if (default_dictionary->GetString(entry_name, &result))
    138       return result;
    139   }
    140 
    141   return std::string();
    142 }
    143 
    144 void CheckWallpaperCacheExists(const base::FilePath& path, bool* exists) {
    145   DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    146   DCHECK(exists);
    147   *exists = base::PathExists(path);
    148 }
    149 
    150 }  // anonymous namespace
    151 
    152 // Template URL where to fetch OEM services customization manifest from.
    153 const char ServicesCustomizationDocument::kManifestUrl[] =
    154     "https://ssl.gstatic.com/chrome/chromeos-customization/%s.json";
    155 
    156 // A custom extensions::ExternalLoader that the ServicesCustomizationDocument
    157 // creates and uses to publish OEM default apps to the extensions system.
    158 class ServicesCustomizationExternalLoader
    159     : public extensions::ExternalLoader,
    160       public base::SupportsWeakPtr<ServicesCustomizationExternalLoader> {
    161  public:
    162   explicit ServicesCustomizationExternalLoader(Profile* profile)
    163       : is_apps_set_(false), profile_(profile) {}
    164 
    165   Profile* profile() { return profile_; }
    166 
    167   // Used by the ServicesCustomizationDocument to update the current apps.
    168   void SetCurrentApps(scoped_ptr<base::DictionaryValue> prefs) {
    169     apps_.Swap(prefs.get());
    170     is_apps_set_ = true;
    171     StartLoading();
    172   }
    173 
    174   // Implementation of extensions::ExternalLoader:
    175   virtual void StartLoading() OVERRIDE {
    176     if (!is_apps_set_) {
    177       ServicesCustomizationDocument::GetInstance()->StartFetching();
    178       // In case of missing customization ID, SetCurrentApps will be called
    179       // synchronously from StartFetching and this function will be called
    180       // recursively so we need to return to avoid calling LoadFinished twice.
    181       // In case of async load it is safe to return empty list because this
    182       // provider didn't install any app yet so no app can be removed due to
    183       // returning empty list.
    184       if (is_apps_set_)
    185         return;
    186     }
    187 
    188     prefs_.reset(apps_.DeepCopy());
    189     VLOG(1) << "ServicesCustomization extension loader publishing "
    190             << apps_.size() << " apps.";
    191     LoadFinished();
    192   }
    193 
    194  protected:
    195   virtual ~ServicesCustomizationExternalLoader() {}
    196 
    197  private:
    198   bool is_apps_set_;
    199   base::DictionaryValue apps_;
    200   Profile* profile_;
    201 
    202   DISALLOW_COPY_AND_ASSIGN(ServicesCustomizationExternalLoader);
    203 };
    204 
    205 // CustomizationDocument implementation. ---------------------------------------
    206 
    207 CustomizationDocument::CustomizationDocument(
    208     const std::string& accepted_version)
    209     : accepted_version_(accepted_version) {}
    210 
    211 CustomizationDocument::~CustomizationDocument() {}
    212 
    213 bool CustomizationDocument::LoadManifestFromFile(
    214     const base::FilePath& manifest_path) {
    215   std::string manifest;
    216   if (!base::ReadFileToString(manifest_path, &manifest))
    217     return false;
    218   return LoadManifestFromString(manifest);
    219 }
    220 
    221 bool CustomizationDocument::LoadManifestFromString(
    222     const std::string& manifest) {
    223   int error_code = 0;
    224   std::string error;
    225   scoped_ptr<base::Value> root(base::JSONReader::ReadAndReturnError(manifest,
    226       base::JSON_ALLOW_TRAILING_COMMAS, &error_code, &error));
    227   if (error_code != base::JSONReader::JSON_NO_ERROR)
    228     LOG(ERROR) << error;
    229   DCHECK(root.get() != NULL);
    230   if (root.get() == NULL)
    231     return false;
    232   DCHECK(root->GetType() == base::Value::TYPE_DICTIONARY);
    233   if (root->GetType() == base::Value::TYPE_DICTIONARY) {
    234     root_.reset(static_cast<base::DictionaryValue*>(root.release()));
    235     std::string result;
    236     if (root_->GetString(kVersionAttr, &result) &&
    237         result == accepted_version_)
    238       return true;
    239 
    240     LOG(ERROR) << "Wrong customization manifest version";
    241     root_.reset(NULL);
    242   }
    243   return false;
    244 }
    245 
    246 std::string CustomizationDocument::GetLocaleSpecificString(
    247     const std::string& locale,
    248     const std::string& dictionary_name,
    249     const std::string& entry_name) const {
    250   return GetLocaleSpecificStringImpl(
    251       root_.get(), locale, dictionary_name, entry_name);
    252 }
    253 
    254 // StartupCustomizationDocument implementation. --------------------------------
    255 
    256 StartupCustomizationDocument::StartupCustomizationDocument()
    257     : CustomizationDocument(kAcceptedManifestVersion) {
    258   {
    259     // Loading manifest causes us to do blocking IO on UI thread.
    260     // Temporarily allow it until we fix http://crosbug.com/11103
    261     base::ThreadRestrictions::ScopedAllowIO allow_io;
    262     LoadManifestFromFile(base::FilePath(kStartupCustomizationManifestPath));
    263   }
    264   Init(system::StatisticsProvider::GetInstance());
    265 }
    266 
    267 StartupCustomizationDocument::StartupCustomizationDocument(
    268     system::StatisticsProvider* statistics_provider,
    269     const std::string& manifest)
    270     : CustomizationDocument(kAcceptedManifestVersion) {
    271   LoadManifestFromString(manifest);
    272   Init(statistics_provider);
    273 }
    274 
    275 StartupCustomizationDocument::~StartupCustomizationDocument() {}
    276 
    277 StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() {
    278   return Singleton<StartupCustomizationDocument,
    279       DefaultSingletonTraits<StartupCustomizationDocument> >::get();
    280 }
    281 
    282 void StartupCustomizationDocument::Init(
    283     system::StatisticsProvider* statistics_provider) {
    284   if (IsReady()) {
    285     root_->GetString(kInitialLocaleAttr, &initial_locale_);
    286     root_->GetString(kInitialTimezoneAttr, &initial_timezone_);
    287     root_->GetString(kKeyboardLayoutAttr, &keyboard_layout_);
    288 
    289     std::string hwid;
    290     if (statistics_provider->GetMachineStatistic(
    291             system::kHardwareClassKey, &hwid)) {
    292       base::ListValue* hwid_list = NULL;
    293       if (root_->GetList(kHwidMapAttr, &hwid_list)) {
    294         for (size_t i = 0; i < hwid_list->GetSize(); ++i) {
    295           base::DictionaryValue* hwid_dictionary = NULL;
    296           std::string hwid_mask;
    297           if (hwid_list->GetDictionary(i, &hwid_dictionary) &&
    298               hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) {
    299             if (MatchPattern(hwid, hwid_mask)) {
    300               // If HWID for this machine matches some mask, use HWID specific
    301               // settings.
    302               std::string result;
    303               if (hwid_dictionary->GetString(kInitialLocaleAttr, &result))
    304                 initial_locale_ = result;
    305 
    306               if (hwid_dictionary->GetString(kInitialTimezoneAttr, &result))
    307                 initial_timezone_ = result;
    308 
    309               if (hwid_dictionary->GetString(kKeyboardLayoutAttr, &result))
    310                 keyboard_layout_ = result;
    311             }
    312             // Don't break here to allow other entires to be applied if match.
    313           } else {
    314             LOG(ERROR) << "Syntax error in customization manifest";
    315           }
    316         }
    317       }
    318     } else {
    319       LOG(ERROR) << "HWID is missing in machine statistics";
    320     }
    321   }
    322 
    323   // If manifest doesn't exist still apply values from VPD.
    324   statistics_provider->GetMachineStatistic(kInitialLocaleAttr,
    325                                            &initial_locale_);
    326   statistics_provider->GetMachineStatistic(kInitialTimezoneAttr,
    327                                            &initial_timezone_);
    328   statistics_provider->GetMachineStatistic(kKeyboardLayoutAttr,
    329                                            &keyboard_layout_);
    330   configured_locales_.resize(0);
    331   base::SplitString(initial_locale_, ',', &configured_locales_);
    332 
    333   // Convert ICU locale to chrome ("en_US" to "en-US", etc.).
    334   std::for_each(configured_locales_.begin(),
    335                 configured_locales_.end(),
    336                 l10n_util::GetCanonicalLocale);
    337 
    338   // Let's always have configured_locales_.front() a valid entry.
    339   if (configured_locales_.size() == 0)
    340     configured_locales_.push_back(std::string());
    341 }
    342 
    343 const std::vector<std::string>&
    344 StartupCustomizationDocument::configured_locales() const {
    345   return configured_locales_;
    346 }
    347 
    348 const std::string& StartupCustomizationDocument::initial_locale_default()
    349     const {
    350   DCHECK(configured_locales_.size() > 0);
    351   return configured_locales_.front();
    352 }
    353 
    354 std::string StartupCustomizationDocument::GetEULAPage(
    355     const std::string& locale) const {
    356   return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
    357 }
    358 
    359 // ServicesCustomizationDocument implementation. -------------------------------
    360 
    361 class ServicesCustomizationDocument::ApplyingTask {
    362  public:
    363   // Registers in ServicesCustomizationDocument;
    364   explicit ApplyingTask(ServicesCustomizationDocument* document);
    365 
    366   // Do not automatically deregister as we might be called on invalid thread.
    367   ~ApplyingTask();
    368 
    369   // Mark task finished and check for customization applied.
    370   void Finished(bool success);
    371 
    372  private:
    373   ServicesCustomizationDocument* document_;
    374 
    375   // This is error-checking flag to prevent destroying unfinished task
    376   // or double finish.
    377   bool engaged_;
    378 };
    379 
    380 ServicesCustomizationDocument::ApplyingTask::ApplyingTask(
    381     ServicesCustomizationDocument* document)
    382     : document_(document), engaged_(true) {
    383   document->ApplyingTaskStarted();
    384 }
    385 
    386 ServicesCustomizationDocument::ApplyingTask::~ApplyingTask() {
    387   DCHECK(!engaged_);
    388 }
    389 
    390 void ServicesCustomizationDocument::ApplyingTask::Finished(bool success) {
    391   DCHECK(engaged_);
    392   if (engaged_) {
    393     engaged_ = false;
    394     document_->ApplyingTaskFinished(success);
    395   }
    396 }
    397 
    398 ServicesCustomizationDocument::ServicesCustomizationDocument()
    399     : CustomizationDocument(kAcceptedManifestVersion),
    400       num_retries_(0),
    401       fetch_started_(false),
    402       network_delay_(
    403           base::TimeDelta::FromMilliseconds(kDefaultNetworkRetryDelayMS)),
    404       apply_tasks_started_(0),
    405       apply_tasks_finished_(0),
    406       apply_tasks_success_(0),
    407       weak_ptr_factory_(this) {
    408 }
    409 
    410 ServicesCustomizationDocument::ServicesCustomizationDocument(
    411     const std::string& manifest)
    412     : CustomizationDocument(kAcceptedManifestVersion),
    413       network_delay_(
    414           base::TimeDelta::FromMilliseconds(kDefaultNetworkRetryDelayMS)),
    415       apply_tasks_started_(0),
    416       apply_tasks_finished_(0),
    417       apply_tasks_success_(0),
    418       weak_ptr_factory_(this) {
    419   LoadManifestFromString(manifest);
    420 }
    421 
    422 ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
    423 
    424 // static
    425 ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() {
    426   if (g_test_services_customization_document)
    427     return g_test_services_customization_document;
    428 
    429   return Singleton<ServicesCustomizationDocument,
    430       DefaultSingletonTraits<ServicesCustomizationDocument> >::get();
    431 }
    432 
    433 // static
    434 void ServicesCustomizationDocument::RegisterPrefs(
    435     PrefRegistrySimple* registry) {
    436   registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
    437   registry->RegisterStringPref(prefs::kCustomizationDefaultWallpaperURL,
    438                                std::string());
    439 }
    440 
    441 // static
    442 void ServicesCustomizationDocument::RegisterProfilePrefs(
    443     user_prefs::PrefRegistrySyncable* registry) {
    444   registry->RegisterDictionaryPref(
    445       kServicesCustomizationKey,
    446       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    447 }
    448 
    449 // static
    450 bool ServicesCustomizationDocument::WasOOBECustomizationApplied() {
    451   PrefService* prefs = g_browser_process->local_state();
    452   // prefs can be NULL in some tests.
    453   if (prefs)
    454     return prefs->GetBoolean(kServicesCustomizationAppliedPref);
    455   else
    456     return false;
    457 }
    458 
    459 // static
    460 void ServicesCustomizationDocument::SetApplied(bool val) {
    461   PrefService* prefs = g_browser_process->local_state();
    462   // prefs can be NULL in some tests.
    463   if (prefs)
    464     prefs->SetBoolean(kServicesCustomizationAppliedPref, val);
    465 }
    466 
    467 // static
    468 base::FilePath ServicesCustomizationDocument::GetCustomizedWallpaperCacheDir() {
    469   base::FilePath custom_wallpaper_dir;
    470   if (!PathService::Get(chrome::DIR_CHROMEOS_CUSTOM_WALLPAPERS,
    471                         &custom_wallpaper_dir)) {
    472     LOG(DFATAL) << "Unable to get custom wallpaper dir.";
    473     return base::FilePath();
    474   }
    475   return custom_wallpaper_dir.Append(kCustomizationDefaultWallpaperDir);
    476 }
    477 
    478 // static
    479 base::FilePath
    480 ServicesCustomizationDocument::GetCustomizedWallpaperDownloadedFileName() {
    481   const base::FilePath dir = GetCustomizedWallpaperCacheDir();
    482   if (dir.empty()) {
    483     NOTREACHED();
    484     return dir;
    485   }
    486   return dir.Append(kCustomizationDefaultWallpaperDownloadedFile);
    487 }
    488 
    489 void ServicesCustomizationDocument::EnsureCustomizationApplied() {
    490   if (WasOOBECustomizationApplied())
    491     return;
    492 
    493   // When customization manifest is fetched, applying will start automatically.
    494   if (IsReady())
    495     return;
    496 
    497   StartFetching();
    498 }
    499 
    500 base::Closure
    501 ServicesCustomizationDocument::EnsureCustomizationAppliedClosure() {
    502   return base::Bind(&ServicesCustomizationDocument::EnsureCustomizationApplied,
    503                     weak_ptr_factory_.GetWeakPtr());
    504 }
    505 
    506 void ServicesCustomizationDocument::StartFetching() {
    507   if (IsReady() || fetch_started_)
    508     return;
    509 
    510   if (!url_.is_valid()) {
    511     std::string customization_id;
    512     chromeos::system::StatisticsProvider* provider =
    513         chromeos::system::StatisticsProvider::GetInstance();
    514     if (provider->GetMachineStatistic(system::kCustomizationIdKey,
    515                                       &customization_id) &&
    516         !customization_id.empty()) {
    517       url_ = GURL(base::StringPrintf(
    518           kManifestUrl, StringToLowerASCII(customization_id).c_str()));
    519     } else {
    520       // Remember that there is no customization ID in VPD.
    521       OnCustomizationNotFound();
    522       return;
    523     }
    524   }
    525 
    526   if (url_.is_valid()) {
    527     fetch_started_ = true;
    528     if (url_.SchemeIsFile()) {
    529       BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    530           base::Bind(&ServicesCustomizationDocument::ReadFileInBackground,
    531                      weak_ptr_factory_.GetWeakPtr(),
    532                      base::FilePath(url_.path())));
    533     } else {
    534       StartFileFetch();
    535     }
    536   }
    537 }
    538 
    539 // static
    540 void ServicesCustomizationDocument::ReadFileInBackground(
    541     base::WeakPtr<ServicesCustomizationDocument> self,
    542     const base::FilePath& file) {
    543   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    544 
    545   std::string manifest;
    546   if (!base::ReadFileToString(file, &manifest)) {
    547     manifest.clear();
    548     LOG(ERROR) << "Failed to load services customization manifest from: "
    549                << file.value();
    550   }
    551 
    552   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    553       base::Bind(&ServicesCustomizationDocument::OnManifesteRead,
    554                  self,
    555                  manifest));
    556 }
    557 
    558 void ServicesCustomizationDocument::OnManifesteRead(
    559     const std::string& manifest) {
    560   if (!manifest.empty())
    561     LoadManifestFromString(manifest);
    562 
    563   fetch_started_ = false;
    564 }
    565 
    566 void ServicesCustomizationDocument::StartFileFetch() {
    567   DelayNetworkCall(base::Bind(&ServicesCustomizationDocument::DoStartFileFetch,
    568                               weak_ptr_factory_.GetWeakPtr()),
    569       network_delay_);
    570 }
    571 
    572 void ServicesCustomizationDocument::DoStartFileFetch() {
    573   url_fetcher_.reset(net::URLFetcher::Create(
    574       url_, net::URLFetcher::GET, this));
    575   url_fetcher_->SetRequestContext(g_browser_process->system_request_context());
    576   url_fetcher_->AddExtraRequestHeader("Accept: application/json");
    577   url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
    578                              net::LOAD_DO_NOT_SAVE_COOKIES |
    579                              net::LOAD_DISABLE_CACHE |
    580                              net::LOAD_DO_NOT_SEND_AUTH_DATA);
    581   url_fetcher_->Start();
    582 }
    583 
    584 bool ServicesCustomizationDocument::LoadManifestFromString(
    585     const std::string& manifest) {
    586   if (CustomizationDocument::LoadManifestFromString(manifest)) {
    587     LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_SUCCESS);
    588     OnManifestLoaded();
    589     return true;
    590   }
    591 
    592   LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_PARSING_ERROR);
    593   return false;
    594 }
    595 
    596 void ServicesCustomizationDocument::OnManifestLoaded() {
    597   if (!WasOOBECustomizationApplied())
    598     ApplyOOBECustomization();
    599 
    600   scoped_ptr<base::DictionaryValue> prefs =
    601       GetDefaultAppsInProviderFormat(*root_);
    602   for (ExternalLoaders::iterator it = external_loaders_.begin();
    603        it != external_loaders_.end(); ++it) {
    604     if (*it) {
    605       UpdateCachedManifest((*it)->profile());
    606       (*it)->SetCurrentApps(
    607           scoped_ptr<base::DictionaryValue>(prefs->DeepCopy()));
    608       SetOemFolderName((*it)->profile(), *root_);
    609     }
    610   }
    611 }
    612 
    613 void ServicesCustomizationDocument::OnURLFetchComplete(
    614     const net::URLFetcher* source) {
    615   std::string mime_type;
    616   std::string data;
    617   if (source->GetStatus().is_success() &&
    618       source->GetResponseCode() == net::HTTP_OK &&
    619       source->GetResponseHeaders()->GetMimeType(&mime_type) &&
    620       mime_type == "application/json" &&
    621       source->GetResponseAsString(&data)) {
    622     LoadManifestFromString(data);
    623   } else if (source->GetResponseCode() == net::HTTP_NOT_FOUND) {
    624     LOG(ERROR) << "Customization manifest is missing on server: "
    625                << source->GetURL().spec();
    626     OnCustomizationNotFound();
    627   } else {
    628     if (num_retries_ < kMaxFetchRetries) {
    629       num_retries_++;
    630       content::BrowserThread::PostDelayedTask(
    631           content::BrowserThread::UI,
    632           FROM_HERE,
    633           base::Bind(&ServicesCustomizationDocument::StartFileFetch,
    634                      weak_ptr_factory_.GetWeakPtr()),
    635           base::TimeDelta::FromSeconds(kRetriesDelayInSec));
    636       return;
    637     }
    638     // This doesn't stop fetching manifest on next restart.
    639     LOG(ERROR) << "URL fetch for services customization failed:"
    640                << " response code = " << source->GetResponseCode()
    641                << " URL = " << source->GetURL().spec();
    642 
    643     LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_RETRIES_FAIL);
    644   }
    645   fetch_started_ = false;
    646 }
    647 
    648 bool ServicesCustomizationDocument::ApplyOOBECustomization() {
    649   if (apply_tasks_started_)
    650     return false;
    651 
    652   CheckAndApplyWallpaper();
    653   return false;
    654 }
    655 
    656 bool ServicesCustomizationDocument::GetDefaultWallpaperUrl(
    657     GURL* out_url) const {
    658   if (!IsReady())
    659     return false;
    660 
    661   std::string url;
    662   if (!root_->GetString(kDefaultWallpaperAttr, &url))
    663     return false;
    664 
    665   *out_url = GURL(url);
    666   return true;
    667 }
    668 
    669 bool ServicesCustomizationDocument::GetDefaultApps(
    670     std::vector<std::string>* ids) const {
    671   ids->clear();
    672   if (!IsReady())
    673     return false;
    674 
    675   base::ListValue* apps_list = NULL;
    676   if (!root_->GetList(kDefaultAppsAttr, &apps_list))
    677     return false;
    678 
    679   for (size_t i = 0; i < apps_list->GetSize(); ++i) {
    680     std::string app_id;
    681     if (apps_list->GetString(i, &app_id)) {
    682       ids->push_back(app_id);
    683     } else {
    684       LOG(ERROR) << "Wrong format of default application list";
    685       return false;
    686     }
    687   }
    688 
    689   return true;
    690 }
    691 
    692 std::string ServicesCustomizationDocument::GetOemAppsFolderName(
    693     const std::string& locale) const {
    694   if (!IsReady())
    695     return std::string();
    696 
    697   return GetOemAppsFolderNameImpl(locale, *root_);
    698 }
    699 
    700 scoped_ptr<base::DictionaryValue>
    701 ServicesCustomizationDocument::GetDefaultAppsInProviderFormat(
    702     const base::DictionaryValue& root) {
    703   scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue);
    704   const base::ListValue* apps_list = NULL;
    705   if (root.GetList(kDefaultAppsAttr, &apps_list)) {
    706     for (size_t i = 0; i < apps_list->GetSize(); ++i) {
    707       std::string app_id;
    708       if (apps_list->GetString(i, &app_id)) {
    709         base::DictionaryValue* entry = new base::DictionaryValue;
    710         entry->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
    711                          extension_urls::GetWebstoreUpdateUrl().spec());
    712         prefs->Set(app_id, entry);
    713       } else {
    714         LOG(ERROR) << "Wrong format of default application list";
    715         prefs->Clear();
    716         break;
    717       }
    718     }
    719   }
    720 
    721   return prefs.Pass();
    722 }
    723 
    724 void ServicesCustomizationDocument::UpdateCachedManifest(Profile* profile) {
    725   profile->GetPrefs()->Set(kServicesCustomizationKey, *root_);
    726 }
    727 
    728 extensions::ExternalLoader* ServicesCustomizationDocument::CreateExternalLoader(
    729     Profile* profile) {
    730   ServicesCustomizationExternalLoader* loader =
    731       new ServicesCustomizationExternalLoader(profile);
    732   external_loaders_.push_back(loader->AsWeakPtr());
    733 
    734   if (IsReady()) {
    735     UpdateCachedManifest(profile);
    736     loader->SetCurrentApps(GetDefaultAppsInProviderFormat(*root_));
    737     SetOemFolderName(profile, *root_);
    738   } else {
    739     const base::DictionaryValue* root =
    740         profile->GetPrefs()->GetDictionary(kServicesCustomizationKey);
    741     std::string version;
    742     if (root && root->GetString(kVersionAttr, &version)) {
    743       // If version exists, profile has cached version of customization.
    744       loader->SetCurrentApps(GetDefaultAppsInProviderFormat(*root));
    745       SetOemFolderName(profile, *root);
    746     } else {
    747       // StartFetching will be called from ServicesCustomizationExternalLoader
    748       // when StartLoading is called. We can't initiate manifest fetch here
    749       // because caller may never call StartLoading for the provider.
    750     }
    751   }
    752 
    753   return loader;
    754 }
    755 
    756 void ServicesCustomizationDocument::OnCustomizationNotFound() {
    757   LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND);
    758   LoadManifestFromString(kEmptyServicesCustomizationManifest);
    759 }
    760 
    761 void ServicesCustomizationDocument::SetOemFolderName(
    762     Profile* profile,
    763     const base::DictionaryValue& root) {
    764   std::string locale = g_browser_process->GetApplicationLocale();
    765   std::string name = GetOemAppsFolderNameImpl(locale, root);
    766   if (name.empty())
    767     name = default_app_order::GetOemAppsFolderName();
    768   if (!name.empty()) {
    769     app_list::AppListSyncableService* service =
    770         app_list::AppListSyncableServiceFactory::GetForProfile(profile);
    771     if (!service) {
    772       LOG(WARNING) << "AppListSyncableService is not ready for setting OEM "
    773                       "folder name";
    774       return;
    775     }
    776     service->SetOemFolderName(name);
    777   }
    778 }
    779 
    780 std::string ServicesCustomizationDocument::GetOemAppsFolderNameImpl(
    781     const std::string& locale,
    782     const base::DictionaryValue& root) const {
    783   return GetLocaleSpecificStringImpl(
    784       &root, locale, kLocalizedContent, kDefaultAppsFolderName);
    785 }
    786 
    787 // static
    788 void ServicesCustomizationDocument::InitializeForTesting() {
    789   g_test_services_customization_document = new ServicesCustomizationDocument;
    790   g_test_services_customization_document->network_delay_ = base::TimeDelta();
    791 }
    792 
    793 // static
    794 void ServicesCustomizationDocument::ShutdownForTesting() {
    795   delete g_test_services_customization_document;
    796   g_test_services_customization_document = NULL;
    797 }
    798 
    799 void ServicesCustomizationDocument::StartOEMWallpaperDownload(
    800     const GURL& wallpaper_url,
    801     scoped_ptr<ServicesCustomizationDocument::ApplyingTask> applying) {
    802   DCHECK(wallpaper_url.is_valid());
    803 
    804   const base::FilePath dir = GetCustomizedWallpaperCacheDir();
    805   const base::FilePath file = GetCustomizedWallpaperDownloadedFileName();
    806   if (dir.empty() || file.empty()) {
    807     NOTREACHED();
    808     applying->Finished(false);
    809     return;
    810   }
    811 
    812   wallpaper_downloader_.reset(new CustomizationWallpaperDownloader(
    813       g_browser_process->system_request_context(),
    814       wallpaper_url,
    815       dir,
    816       file,
    817       base::Bind(&ServicesCustomizationDocument::OnOEMWallpaperDownloaded,
    818                  weak_ptr_factory_.GetWeakPtr(),
    819                  base::Passed(applying.Pass()))));
    820 
    821   wallpaper_downloader_->Start();
    822 }
    823 
    824 void ServicesCustomizationDocument::CheckAndApplyWallpaper() {
    825   if (wallpaper_downloader_.get()) {
    826     VLOG(1) << "CheckAndApplyWallpaper(): download has already started.";
    827     return;
    828   }
    829   scoped_ptr<ServicesCustomizationDocument::ApplyingTask> applying(
    830       new ServicesCustomizationDocument::ApplyingTask(this));
    831 
    832   GURL wallpaper_url;
    833   if (!GetDefaultWallpaperUrl(&wallpaper_url)) {
    834     PrefService* pref_service = g_browser_process->local_state();
    835     std::string current_url =
    836         pref_service->GetString(prefs::kCustomizationDefaultWallpaperURL);
    837     if (!current_url.empty()) {
    838       VLOG(1) << "ServicesCustomizationDocument::CheckAndApplyWallpaper() : "
    839               << "No wallpaper URL attribute in customization document, "
    840               << "but current value is non-empty: '" << current_url
    841               << "'. Ignored.";
    842     }
    843     applying->Finished(true);
    844     return;
    845   }
    846 
    847   // Should fail if this ever happens in tests.
    848   DCHECK(wallpaper_url.is_valid());
    849   if (!wallpaper_url.is_valid()) {
    850     if (!wallpaper_url.is_empty()) {
    851       LOG(WARNING) << "Invalid Customized Wallpaper URL '"
    852                    << wallpaper_url.spec() << "'.";
    853     }
    854     applying->Finished(false);
    855     return;
    856   }
    857 
    858   scoped_ptr<bool> exists(new bool(false));
    859 
    860   base::Closure check_file_exists =
    861       base::Bind(&CheckWallpaperCacheExists,
    862                  GetCustomizedWallpaperDownloadedFileName(),
    863                  base::Unretained(exists.get()));
    864   base::Closure on_checked_closure =
    865       base::Bind(&ServicesCustomizationDocument::OnCheckedWallpaperCacheExists,
    866                  weak_ptr_factory_.GetWeakPtr(),
    867                  base::Passed(exists.Pass()),
    868                  base::Passed(applying.Pass()));
    869   if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
    870           FROM_HERE, check_file_exists, on_checked_closure)) {
    871     LOG(WARNING) << "Failed to start check Wallpaper cache exists.";
    872   }
    873 }
    874 
    875 void ServicesCustomizationDocument::OnCheckedWallpaperCacheExists(
    876     scoped_ptr<bool> exists,
    877     scoped_ptr<ServicesCustomizationDocument::ApplyingTask> applying) {
    878   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    879   DCHECK(exists);
    880   DCHECK(applying);
    881 
    882   ApplyWallpaper(*exists, applying.Pass());
    883 }
    884 
    885 void ServicesCustomizationDocument::ApplyWallpaper(
    886     bool default_wallpaper_file_exists,
    887     scoped_ptr<ServicesCustomizationDocument::ApplyingTask> applying) {
    888   GURL wallpaper_url;
    889   const bool wallpaper_url_present = GetDefaultWallpaperUrl(&wallpaper_url);
    890 
    891   PrefService* pref_service = g_browser_process->local_state();
    892 
    893   std::string current_url =
    894       pref_service->GetString(prefs::kCustomizationDefaultWallpaperURL);
    895   if (current_url != wallpaper_url.spec()) {
    896     if (wallpaper_url_present) {
    897       VLOG(1) << "ServicesCustomizationDocument::ApplyWallpaper() : "
    898               << "Wallpaper URL in customization document '"
    899               << wallpaper_url.spec() << "' differs from current '"
    900               << current_url << "'."
    901               << (GURL(current_url).is_valid() && default_wallpaper_file_exists
    902                       ? " Ignored."
    903                       : " Will refetch.");
    904     } else {
    905       VLOG(1) << "ServicesCustomizationDocument::ApplyWallpaper() : "
    906               << "No wallpaper URL attribute in customization document, "
    907               << "but current value is non-empty: '" << current_url
    908               << "'. Ignored.";
    909     }
    910   }
    911   if (!wallpaper_url_present) {
    912     applying->Finished(true);
    913     return;
    914   }
    915 
    916   DCHECK(wallpaper_url.is_valid());
    917 
    918   // Never update system-wide wallpaper (i.e. do not check
    919   // current_url == wallpaper_url.spec() )
    920   if (GURL(current_url).is_valid() && default_wallpaper_file_exists) {
    921     VLOG(1)
    922         << "ServicesCustomizationDocument::ApplyWallpaper() : reuse existing";
    923     OnOEMWallpaperDownloaded(applying.Pass(), true, GURL(current_url));
    924   } else {
    925     VLOG(1)
    926         << "ServicesCustomizationDocument::ApplyWallpaper() : start download";
    927     StartOEMWallpaperDownload(wallpaper_url, applying.Pass());
    928   }
    929 }
    930 
    931 void ServicesCustomizationDocument::OnOEMWallpaperDownloaded(
    932     scoped_ptr<ServicesCustomizationDocument::ApplyingTask> applying,
    933     bool success,
    934     const GURL& wallpaper_url) {
    935   if (success) {
    936     DCHECK(wallpaper_url.is_valid());
    937 
    938     VLOG(1) << "Setting default wallpaper to '"
    939             << GetCustomizedWallpaperDownloadedFileName().value() << "' ('"
    940             << wallpaper_url.spec() << "')";
    941     WallpaperManager::Get()->SetCustomizedDefaultWallpaper(
    942         wallpaper_url,
    943         GetCustomizedWallpaperDownloadedFileName(),
    944         GetCustomizedWallpaperCacheDir());
    945   }
    946   wallpaper_downloader_.reset();
    947   applying->Finished(success);
    948 }
    949 
    950 void ServicesCustomizationDocument::ApplyingTaskStarted() {
    951   ++apply_tasks_started_;
    952 }
    953 
    954 void ServicesCustomizationDocument::ApplyingTaskFinished(bool success) {
    955   DCHECK_GT(apply_tasks_started_, apply_tasks_finished_);
    956   ++apply_tasks_finished_;
    957 
    958   apply_tasks_success_ += success;
    959 
    960   if (apply_tasks_started_ != apply_tasks_finished_)
    961     return;
    962 
    963   if (apply_tasks_success_ == apply_tasks_finished_)
    964     SetApplied(true);
    965 }
    966 
    967 }  // namespace chromeos
    968