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/files/file_path.h"
12#include "base/files/file_util.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/pref_names.h"
38#include "chromeos/system/statistics_provider.h"
39#include "components/pref_registry/pref_registry_syncable.h"
40#include "content/public/browser/browser_thread.h"
41#include "extensions/common/extension_urls.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
48using content::BrowserThread;
49
50namespace chromeos {
51namespace {
52
53  // Manifest attributes names.
54const char kVersionAttr[] = "version";
55const char kDefaultAttr[] = "default";
56const char kInitialLocaleAttr[] = "initial_locale";
57const char kInitialTimezoneAttr[] = "initial_timezone";
58const char kKeyboardLayoutAttr[] = "keyboard_layout";
59const char kHwidMapAttr[] = "hwid_map";
60const char kHwidMaskAttr[] = "hwid_mask";
61const char kSetupContentAttr[] = "setup_content";
62const char kEulaPageAttr[] = "eula_page";
63const char kDefaultWallpaperAttr[] = "default_wallpaper";
64const char kDefaultAppsAttr[] = "default_apps";
65const char kLocalizedContent[] = "localized_content";
66const char kDefaultAppsFolderName[] = "default_apps_folder_name";
67
68const char kAcceptedManifestVersion[] = "1.0";
69
70// Path to OEM partner startup customization manifest.
71const 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.
76const char kCustomizationDefaultWallpaperDir[] = "customization";
77
78// The original downloaded image file is stored under this name.
79const char kCustomizationDefaultWallpaperDownloadedFile[] =
80    "default_downloaded_wallpaper.bin";
81
82// Name of local state option that tracks if services customization has been
83// applied.
84const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied";
85
86// Maximum number of retries to fetch file if network is not available.
87const int kMaxFetchRetries = 3;
88
89// Delay between file fetch retries if network is not available.
90const int kRetriesDelayInSec = 2;
91
92// Name of profile option that tracks cached version of service customization.
93const char kServicesCustomizationKey[] = "customization.manifest_cache";
94
95// Empty customization document that doesn't customize anything.
96const char kEmptyServicesCustomizationManifest[] = "{ \"version\": \"1.0\" }";
97
98// Global overrider for ServicesCustomizationDocument for tests.
99ServicesCustomizationDocument* 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!
104enum 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
112void LogManifestLoadResult(HistogramServicesCustomizationLoadResult result) {
113  UMA_HISTOGRAM_ENUMERATION("ServicesCustomization.LoadResult",
114                            result,
115                            HISTOGRAM_LOAD_RESULT_MAX_VALUE);
116}
117
118std::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
144void 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.
153const 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.
158class 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
207CustomizationDocument::CustomizationDocument(
208    const std::string& accepted_version)
209    : accepted_version_(accepted_version) {}
210
211CustomizationDocument::~CustomizationDocument() {}
212
213bool 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
221bool 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
246std::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
256StartupCustomizationDocument::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
267StartupCustomizationDocument::StartupCustomizationDocument(
268    system::StatisticsProvider* statistics_provider,
269    const std::string& manifest)
270    : CustomizationDocument(kAcceptedManifestVersion) {
271  LoadManifestFromString(manifest);
272  Init(statistics_provider);
273}
274
275StartupCustomizationDocument::~StartupCustomizationDocument() {}
276
277StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() {
278  return Singleton<StartupCustomizationDocument,
279      DefaultSingletonTraits<StartupCustomizationDocument> >::get();
280}
281
282void 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
343const std::vector<std::string>&
344StartupCustomizationDocument::configured_locales() const {
345  return configured_locales_;
346}
347
348const std::string& StartupCustomizationDocument::initial_locale_default()
349    const {
350  DCHECK(configured_locales_.size() > 0);
351  return configured_locales_.front();
352}
353
354std::string StartupCustomizationDocument::GetEULAPage(
355    const std::string& locale) const {
356  return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
357}
358
359// ServicesCustomizationDocument implementation. -------------------------------
360
361class 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
380ServicesCustomizationDocument::ApplyingTask::ApplyingTask(
381    ServicesCustomizationDocument* document)
382    : document_(document), engaged_(true) {
383  document->ApplyingTaskStarted();
384}
385
386ServicesCustomizationDocument::ApplyingTask::~ApplyingTask() {
387  DCHECK(!engaged_);
388}
389
390void ServicesCustomizationDocument::ApplyingTask::Finished(bool success) {
391  DCHECK(engaged_);
392  if (engaged_) {
393    engaged_ = false;
394    document_->ApplyingTaskFinished(success);
395  }
396}
397
398ServicesCustomizationDocument::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
410ServicesCustomizationDocument::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
422ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
423
424// static
425ServicesCustomizationDocument* 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
434void ServicesCustomizationDocument::RegisterPrefs(
435    PrefRegistrySimple* registry) {
436  registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
437  registry->RegisterStringPref(prefs::kCustomizationDefaultWallpaperURL,
438                               std::string());
439}
440
441// static
442void ServicesCustomizationDocument::RegisterProfilePrefs(
443    user_prefs::PrefRegistrySyncable* registry) {
444  registry->RegisterDictionaryPref(
445      kServicesCustomizationKey,
446      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
447}
448
449// static
450bool 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
460void 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
468base::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
479base::FilePath
480ServicesCustomizationDocument::GetCustomizedWallpaperDownloadedFileName() {
481  const base::FilePath dir = GetCustomizedWallpaperCacheDir();
482  if (dir.empty()) {
483    NOTREACHED();
484    return dir;
485  }
486  return dir.Append(kCustomizationDefaultWallpaperDownloadedFile);
487}
488
489void 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
500base::Closure
501ServicesCustomizationDocument::EnsureCustomizationAppliedClosure() {
502  return base::Bind(&ServicesCustomizationDocument::EnsureCustomizationApplied,
503                    weak_ptr_factory_.GetWeakPtr());
504}
505
506void 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, base::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
540void 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
558void ServicesCustomizationDocument::OnManifesteRead(
559    const std::string& manifest) {
560  if (!manifest.empty())
561    LoadManifestFromString(manifest);
562
563  fetch_started_ = false;
564}
565
566void ServicesCustomizationDocument::StartFileFetch() {
567  DelayNetworkCall(base::Bind(&ServicesCustomizationDocument::DoStartFileFetch,
568                              weak_ptr_factory_.GetWeakPtr()),
569      network_delay_);
570}
571
572void 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
584bool 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
596void 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
613void 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
648bool ServicesCustomizationDocument::ApplyOOBECustomization() {
649  if (apply_tasks_started_)
650    return false;
651
652  CheckAndApplyWallpaper();
653  return false;
654}
655
656bool 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
669bool 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
692std::string ServicesCustomizationDocument::GetOemAppsFolderName(
693    const std::string& locale) const {
694  if (!IsReady())
695    return std::string();
696
697  return GetOemAppsFolderNameImpl(locale, *root_);
698}
699
700scoped_ptr<base::DictionaryValue>
701ServicesCustomizationDocument::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
724void ServicesCustomizationDocument::UpdateCachedManifest(Profile* profile) {
725  profile->GetPrefs()->Set(kServicesCustomizationKey, *root_);
726}
727
728extensions::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
756void ServicesCustomizationDocument::OnCustomizationNotFound() {
757  LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND);
758  LoadManifestFromString(kEmptyServicesCustomizationManifest);
759}
760
761void 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
780std::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
788void ServicesCustomizationDocument::InitializeForTesting() {
789  g_test_services_customization_document = new ServicesCustomizationDocument;
790  g_test_services_customization_document->network_delay_ = base::TimeDelta();
791}
792
793// static
794void ServicesCustomizationDocument::ShutdownForTesting() {
795  delete g_test_services_customization_document;
796  g_test_services_customization_document = NULL;
797}
798
799void 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
824void 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
875void 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
885void 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
931void 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
950void ServicesCustomizationDocument::ApplyingTaskStarted() {
951  ++apply_tasks_started_;
952}
953
954void 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