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.
5 #include "chrome/browser/chromeos/customization_document.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/prefs/pref_registry_simple.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/time/time.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/chromeos/login/wizard_controller.h"
26 #include "chrome/browser/chromeos/net/delay_network_call.h"
27 #include "chrome/browser/extensions/external_loader.h"
28 #include "chrome/browser/extensions/external_provider_impl.h"
29 #include "chrome/browser/profiles/profile.h"
30 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
31 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
32 #include "chrome/common/extensions/extension_constants.h"
33 #include "chromeos/network/network_state.h"
34 #include "chromeos/network/network_state_handler.h"
35 #include "chromeos/system/statistics_provider.h"
36 #include "components/user_prefs/pref_registry_syncable.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "net/base/load_flags.h"
39 #include "net/http/http_response_headers.h"
40 #include "net/http/http_status_code.h"
41 #include "net/url_request/url_fetcher.h"
42 #include "ui/base/l10n/l10n_util.h"
44 using content::BrowserThread;
49 // Manifest attributes names.
50 const char kVersionAttr[] = "version";
51 const char kDefaultAttr[] = "default";
52 const char kInitialLocaleAttr[] = "initial_locale";
53 const char kInitialTimezoneAttr[] = "initial_timezone";
54 const char kKeyboardLayoutAttr[] = "keyboard_layout";
55 const char kHwidMapAttr[] = "hwid_map";
56 const char kHwidMaskAttr[] = "hwid_mask";
57 const char kSetupContentAttr[] = "setup_content";
58 const char kEulaPageAttr[] = "eula_page";
59 const char kDefaultWallpaperAttr[] = "default_wallpaper";
60 const char kDefaultAppsAttr[] = "default_apps";
61 const char kLocalizedContent[] = "localized_content";
62 const char kDefaultAppsFolderName[] = "default_apps_folder_name";
64 const char kAcceptedManifestVersion[] = "1.0";
66 // Path to OEM partner startup customization manifest.
67 const char kStartupCustomizationManifestPath[] =
68 "/opt/oem/etc/startup_manifest.json";
70 // Name of local state option that tracks if services customization has been
72 const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied";
74 // Maximum number of retries to fetch file if network is not available.
75 const int kMaxFetchRetries = 3;
77 // Delay between file fetch retries if network is not available.
78 const int kRetriesDelayInSec = 2;
80 // Name of profile option that tracks cached version of service customization.
81 const char kServicesCustomizationKey[] = "customization.manifest_cache";
83 // Empty customization document that doesn't customize anything.
84 const char kEmptyServicesCustomizationManifest[] = "{ \"version\": \"1.0\" }";
86 // Global overrider for ServicesCustomizationDocument for tests.
87 ServicesCustomizationDocument* g_test_services_customization_document = NULL;
89 // Services customization document load results reported via the
90 // "ServicesCustomization.LoadResult" histogram.
91 // It is append-only enum due to use in a histogram!
92 enum HistogramServicesCustomizationLoadResult {
93 HISTOGRAM_LOAD_RESULT_SUCCESS = 0,
94 HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND = 1,
95 HISTOGRAM_LOAD_RESULT_PARSING_ERROR = 2,
96 HISTOGRAM_LOAD_RESULT_RETRIES_FAIL = 3,
97 HISTOGRAM_LOAD_RESULT_MAX_VALUE = 4
100 void LogManifestLoadResult(HistogramServicesCustomizationLoadResult result) {
101 UMA_HISTOGRAM_ENUMERATION("ServicesCustomization.LoadResult",
103 HISTOGRAM_LOAD_RESULT_MAX_VALUE);
106 std::string GetLocaleSpecificStringImpl(
107 const base::DictionaryValue* root,
108 const std::string& locale,
109 const std::string& dictionary_name,
110 const std::string& entry_name) {
111 const base::DictionaryValue* dictionary_content = NULL;
112 if (!root || !root->GetDictionary(dictionary_name, &dictionary_content))
113 return std::string();
115 const base::DictionaryValue* locale_dictionary = NULL;
116 if (dictionary_content->GetDictionary(locale, &locale_dictionary)) {
118 if (locale_dictionary->GetString(entry_name, &result))
122 const base::DictionaryValue* default_dictionary = NULL;
123 if (dictionary_content->GetDictionary(kDefaultAttr, &default_dictionary)) {
125 if (default_dictionary->GetString(entry_name, &result))
129 return std::string();
132 } // anonymous namespace
134 // Template URL where to fetch OEM services customization manifest from.
135 const char ServicesCustomizationDocument::kManifestUrl[] =
136 "https://ssl.gstatic.com/chrome/chromeos-customization/%s.json";
138 // A custom extensions::ExternalLoader that the ServicesCustomizationDocument
139 // creates and uses to publish OEM default apps to the extensions system.
140 class ServicesCustomizationExternalLoader
141 : public extensions::ExternalLoader,
142 public base::SupportsWeakPtr<ServicesCustomizationExternalLoader> {
144 explicit ServicesCustomizationExternalLoader(Profile* profile)
145 : is_apps_set_(false), profile_(profile) {}
147 Profile* profile() { return profile_; }
149 // Used by the ServicesCustomizationDocument to update the current apps.
150 void SetCurrentApps(scoped_ptr<base::DictionaryValue> prefs) {
151 apps_.Swap(prefs.get());
156 // Implementation of extensions::ExternalLoader:
157 virtual void StartLoading() OVERRIDE {
159 ServicesCustomizationDocument::GetInstance()->StartFetching();
160 // No return here to call LoadFinished with empty list initially.
161 // When manifest is fetched, it will be called again with real list.
162 // It is safe to return empty list because this provider didn't install
163 // any app yet so no app can be removed due to returning empty list.
166 prefs_.reset(apps_.DeepCopy());
167 VLOG(1) << "ServicesCustomization extension loader publishing "
168 << apps_.size() << " apps.";
173 virtual ~ServicesCustomizationExternalLoader() {}
177 base::DictionaryValue apps_;
180 DISALLOW_COPY_AND_ASSIGN(ServicesCustomizationExternalLoader);
183 // CustomizationDocument implementation. ---------------------------------------
185 CustomizationDocument::CustomizationDocument(
186 const std::string& accepted_version)
187 : accepted_version_(accepted_version) {}
189 CustomizationDocument::~CustomizationDocument() {}
191 bool CustomizationDocument::LoadManifestFromFile(
192 const base::FilePath& manifest_path) {
193 std::string manifest;
194 if (!base::ReadFileToString(manifest_path, &manifest))
196 return LoadManifestFromString(manifest);
199 bool CustomizationDocument::LoadManifestFromString(
200 const std::string& manifest) {
203 scoped_ptr<base::Value> root(base::JSONReader::ReadAndReturnError(manifest,
204 base::JSON_ALLOW_TRAILING_COMMAS, &error_code, &error));
205 if (error_code != base::JSONReader::JSON_NO_ERROR)
207 DCHECK(root.get() != NULL);
208 if (root.get() == NULL)
210 DCHECK(root->GetType() == base::Value::TYPE_DICTIONARY);
211 if (root->GetType() == base::Value::TYPE_DICTIONARY) {
212 root_.reset(static_cast<base::DictionaryValue*>(root.release()));
214 if (root_->GetString(kVersionAttr, &result) &&
215 result == accepted_version_)
218 LOG(ERROR) << "Wrong customization manifest version";
224 std::string CustomizationDocument::GetLocaleSpecificString(
225 const std::string& locale,
226 const std::string& dictionary_name,
227 const std::string& entry_name) const {
228 return GetLocaleSpecificStringImpl(
229 root_.get(), locale, dictionary_name, entry_name);
232 // StartupCustomizationDocument implementation. --------------------------------
234 StartupCustomizationDocument::StartupCustomizationDocument()
235 : CustomizationDocument(kAcceptedManifestVersion) {
237 // Loading manifest causes us to do blocking IO on UI thread.
238 // Temporarily allow it until we fix http://crosbug.com/11103
239 base::ThreadRestrictions::ScopedAllowIO allow_io;
240 LoadManifestFromFile(base::FilePath(kStartupCustomizationManifestPath));
242 Init(system::StatisticsProvider::GetInstance());
245 StartupCustomizationDocument::StartupCustomizationDocument(
246 system::StatisticsProvider* statistics_provider,
247 const std::string& manifest)
248 : CustomizationDocument(kAcceptedManifestVersion) {
249 LoadManifestFromString(manifest);
250 Init(statistics_provider);
253 StartupCustomizationDocument::~StartupCustomizationDocument() {}
255 StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() {
256 return Singleton<StartupCustomizationDocument,
257 DefaultSingletonTraits<StartupCustomizationDocument> >::get();
260 void StartupCustomizationDocument::Init(
261 system::StatisticsProvider* statistics_provider) {
263 root_->GetString(kInitialLocaleAttr, &initial_locale_);
264 root_->GetString(kInitialTimezoneAttr, &initial_timezone_);
265 root_->GetString(kKeyboardLayoutAttr, &keyboard_layout_);
268 if (statistics_provider->GetMachineStatistic(
269 system::kHardwareClassKey, &hwid)) {
270 base::ListValue* hwid_list = NULL;
271 if (root_->GetList(kHwidMapAttr, &hwid_list)) {
272 for (size_t i = 0; i < hwid_list->GetSize(); ++i) {
273 base::DictionaryValue* hwid_dictionary = NULL;
274 std::string hwid_mask;
275 if (hwid_list->GetDictionary(i, &hwid_dictionary) &&
276 hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) {
277 if (MatchPattern(hwid, hwid_mask)) {
278 // If HWID for this machine matches some mask, use HWID specific
281 if (hwid_dictionary->GetString(kInitialLocaleAttr, &result))
282 initial_locale_ = result;
284 if (hwid_dictionary->GetString(kInitialTimezoneAttr, &result))
285 initial_timezone_ = result;
287 if (hwid_dictionary->GetString(kKeyboardLayoutAttr, &result))
288 keyboard_layout_ = result;
290 // Don't break here to allow other entires to be applied if match.
292 LOG(ERROR) << "Syntax error in customization manifest";
297 LOG(ERROR) << "HWID is missing in machine statistics";
301 // If manifest doesn't exist still apply values from VPD.
302 statistics_provider->GetMachineStatistic(kInitialLocaleAttr,
304 statistics_provider->GetMachineStatistic(kInitialTimezoneAttr,
306 statistics_provider->GetMachineStatistic(kKeyboardLayoutAttr,
308 configured_locales_.resize(0);
309 base::SplitString(initial_locale_, ',', &configured_locales_);
311 // Convert ICU locale to chrome ("en_US" to "en-US", etc.).
312 std::for_each(configured_locales_.begin(),
313 configured_locales_.end(),
314 l10n_util::GetCanonicalLocale);
316 // Let's always have configured_locales_.front() a valid entry.
317 if (configured_locales_.size() == 0)
318 configured_locales_.push_back(std::string());
321 const std::vector<std::string>&
322 StartupCustomizationDocument::configured_locales() const {
323 return configured_locales_;
326 const std::string& StartupCustomizationDocument::initial_locale_default()
328 DCHECK(configured_locales_.size() > 0);
329 return configured_locales_.front();
332 std::string StartupCustomizationDocument::GetEULAPage(
333 const std::string& locale) const {
334 return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
337 // ServicesCustomizationDocument implementation. -------------------------------
339 ServicesCustomizationDocument::ServicesCustomizationDocument()
340 : CustomizationDocument(kAcceptedManifestVersion),
342 fetch_started_(false),
343 network_delay_(base::TimeDelta::FromMilliseconds(
344 kDefaultNetworkRetryDelayMS)),
345 weak_ptr_factory_(this) {
348 ServicesCustomizationDocument::ServicesCustomizationDocument(
349 const std::string& manifest)
350 : CustomizationDocument(kAcceptedManifestVersion),
351 network_delay_(base::TimeDelta::FromMilliseconds(
352 kDefaultNetworkRetryDelayMS)),
353 weak_ptr_factory_(this) {
354 LoadManifestFromString(manifest);
357 ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
360 ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() {
361 if (g_test_services_customization_document)
362 return g_test_services_customization_document;
364 return Singleton<ServicesCustomizationDocument,
365 DefaultSingletonTraits<ServicesCustomizationDocument> >::get();
369 void ServicesCustomizationDocument::RegisterPrefs(
370 PrefRegistrySimple* registry) {
371 registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
375 void ServicesCustomizationDocument::RegisterProfilePrefs(
376 user_prefs::PrefRegistrySyncable* registry) {
377 registry->RegisterDictionaryPref(
378 kServicesCustomizationKey,
379 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
383 bool ServicesCustomizationDocument::WasOOBECustomizationApplied() {
384 PrefService* prefs = g_browser_process->local_state();
385 return prefs->GetBoolean(kServicesCustomizationAppliedPref);
389 void ServicesCustomizationDocument::SetApplied(bool val) {
390 PrefService* prefs = g_browser_process->local_state();
391 prefs->SetBoolean(kServicesCustomizationAppliedPref, val);
394 void ServicesCustomizationDocument::StartFetching() {
395 if (IsReady() || fetch_started_)
398 if (!url_.is_valid()) {
399 std::string customization_id;
400 chromeos::system::StatisticsProvider* provider =
401 chromeos::system::StatisticsProvider::GetInstance();
402 if (provider->GetMachineStatistic(system::kCustomizationIdKey,
403 &customization_id) &&
404 !customization_id.empty()) {
405 url_ = GURL(base::StringPrintf(
406 kManifestUrl, StringToLowerASCII(customization_id).c_str()));
410 if (url_.is_valid()) {
411 fetch_started_ = true;
412 if (url_.SchemeIsFile()) {
413 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
414 base::Bind(&ServicesCustomizationDocument::ReadFileInBackground,
415 weak_ptr_factory_.GetWeakPtr(),
416 base::FilePath(url_.path())));
424 void ServicesCustomizationDocument::ReadFileInBackground(
425 base::WeakPtr<ServicesCustomizationDocument> self,
426 const base::FilePath& file) {
427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
429 std::string manifest;
430 if (!base::ReadFileToString(file, &manifest)) {
432 LOG(ERROR) << "Failed to load services customization manifest from: "
436 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
437 base::Bind(&ServicesCustomizationDocument::OnManifesteRead,
442 void ServicesCustomizationDocument::OnManifesteRead(
443 const std::string& manifest) {
444 if (!manifest.empty())
445 LoadManifestFromString(manifest);
447 fetch_started_ = false;
450 void ServicesCustomizationDocument::StartFileFetch() {
451 DelayNetworkCall(base::Bind(&ServicesCustomizationDocument::DoStartFileFetch,
452 weak_ptr_factory_.GetWeakPtr()),
456 void ServicesCustomizationDocument::DoStartFileFetch() {
457 url_fetcher_.reset(net::URLFetcher::Create(
458 url_, net::URLFetcher::GET, this));
459 url_fetcher_->SetRequestContext(g_browser_process->system_request_context());
460 url_fetcher_->AddExtraRequestHeader("Accept: application/json");
461 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
462 net::LOAD_DO_NOT_SAVE_COOKIES |
463 net::LOAD_DISABLE_CACHE |
464 net::LOAD_DO_NOT_SEND_AUTH_DATA);
465 url_fetcher_->Start();
468 bool ServicesCustomizationDocument::LoadManifestFromString(
469 const std::string& manifest) {
470 if (CustomizationDocument::LoadManifestFromString(manifest)) {
471 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_SUCCESS);
476 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_PARSING_ERROR);
480 void ServicesCustomizationDocument::OnManifestLoaded() {
481 if (!ServicesCustomizationDocument::WasOOBECustomizationApplied())
482 ApplyOOBECustomization();
484 scoped_ptr<base::DictionaryValue> prefs =
485 GetDefaultAppsInProviderFormat(*root_);
486 for (ExternalLoaders::iterator it = external_loaders_.begin();
487 it != external_loaders_.end(); ++it) {
489 UpdateCachedManifest((*it)->profile());
490 (*it)->SetCurrentApps(
491 scoped_ptr<base::DictionaryValue>(prefs->DeepCopy()));
492 SetOemFolderName((*it)->profile(), *root_);
497 void ServicesCustomizationDocument::OnURLFetchComplete(
498 const net::URLFetcher* source) {
499 std::string mime_type;
501 if (source->GetStatus().is_success() &&
502 source->GetResponseCode() == net::HTTP_OK &&
503 source->GetResponseHeaders()->GetMimeType(&mime_type) &&
504 mime_type == "application/json" &&
505 source->GetResponseAsString(&data)) {
506 LoadManifestFromString(data);
507 } else if (source->GetResponseCode() == net::HTTP_NOT_FOUND) {
508 LOG(ERROR) << "Customization manifest is missing on server: "
509 << source->GetURL().spec();
510 OnCustomizationNotFound();
512 if (num_retries_ < kMaxFetchRetries) {
514 content::BrowserThread::PostDelayedTask(
515 content::BrowserThread::UI,
517 base::Bind(&ServicesCustomizationDocument::StartFileFetch,
518 weak_ptr_factory_.GetWeakPtr()),
519 base::TimeDelta::FromSeconds(kRetriesDelayInSec));
522 // This doesn't stop fetching manifest on next restart.
523 LOG(ERROR) << "URL fetch for services customization failed:"
524 << " response code = " << source->GetResponseCode()
525 << " URL = " << source->GetURL().spec();
527 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_RETRIES_FAIL);
529 fetch_started_ = false;
532 bool ServicesCustomizationDocument::ApplyOOBECustomization() {
533 // TODO(dpolukhin): apply default wallpaper, crbug.com/348136.
538 GURL ServicesCustomizationDocument::GetDefaultWallpaperUrl() const {
543 root_->GetString(kDefaultWallpaperAttr, &url);
547 bool ServicesCustomizationDocument::GetDefaultApps(
548 std::vector<std::string>* ids) const {
553 base::ListValue* apps_list = NULL;
554 if (!root_->GetList(kDefaultAppsAttr, &apps_list))
557 for (size_t i = 0; i < apps_list->GetSize(); ++i) {
559 if (apps_list->GetString(i, &app_id)) {
560 ids->push_back(app_id);
562 LOG(ERROR) << "Wrong format of default application list";
570 std::string ServicesCustomizationDocument::GetOemAppsFolderName(
571 const std::string& locale) const {
573 return std::string();
575 return GetOemAppsFolderNameImpl(locale, *root_);
578 scoped_ptr<base::DictionaryValue>
579 ServicesCustomizationDocument::GetDefaultAppsInProviderFormat(
580 const base::DictionaryValue& root) {
581 scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue);
582 const base::ListValue* apps_list = NULL;
583 if (root.GetList(kDefaultAppsAttr, &apps_list)) {
584 for (size_t i = 0; i < apps_list->GetSize(); ++i) {
586 if (apps_list->GetString(i, &app_id)) {
587 base::DictionaryValue* entry = new base::DictionaryValue;
588 entry->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
589 extension_urls::GetWebstoreUpdateUrl().spec());
590 prefs->Set(app_id, entry);
592 LOG(ERROR) << "Wrong format of default application list";
602 void ServicesCustomizationDocument::UpdateCachedManifest(Profile* profile) {
603 profile->GetPrefs()->Set(kServicesCustomizationKey, *root_);
606 extensions::ExternalLoader* ServicesCustomizationDocument::CreateExternalLoader(
608 ServicesCustomizationExternalLoader* loader =
609 new ServicesCustomizationExternalLoader(profile);
610 external_loaders_.push_back(loader->AsWeakPtr());
613 UpdateCachedManifest(profile);
614 loader->SetCurrentApps(GetDefaultAppsInProviderFormat(*root_));
615 SetOemFolderName(profile, *root_);
617 const base::DictionaryValue* root =
618 profile->GetPrefs()->GetDictionary(kServicesCustomizationKey);
620 if (root && root->GetString(kVersionAttr, &version)) {
621 // If version exists, profile has cached version of customization.
622 loader->SetCurrentApps(GetDefaultAppsInProviderFormat(*root));
623 SetOemFolderName(profile, *root);
625 // StartFetching will be called from ServicesCustomizationExternalLoader
626 // when StartLoading is called. We can't initiate manifest fetch here
627 // because caller may never call StartLoading for the provider.
634 void ServicesCustomizationDocument::OnCustomizationNotFound() {
635 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND);
636 LoadManifestFromString(kEmptyServicesCustomizationManifest);
639 void ServicesCustomizationDocument::SetOemFolderName(
641 const base::DictionaryValue& root) {
642 std::string locale = g_browser_process->GetApplicationLocale();
643 std::string name = GetOemAppsFolderNameImpl(locale, root);
645 app_list::AppListSyncableService* service =
646 app_list::AppListSyncableServiceFactory::GetForProfile(profile);
648 LOG(WARNING) << "AppListSyncableService is not ready for setting OEM "
652 service->SetOemFolderName(name);
656 std::string ServicesCustomizationDocument::GetOemAppsFolderNameImpl(
657 const std::string& locale,
658 const base::DictionaryValue& root) const {
659 return GetLocaleSpecificStringImpl(
660 &root, locale, kLocalizedContent, kDefaultAppsFolderName);
664 void ServicesCustomizationDocument::InitializeForTesting() {
665 g_test_services_customization_document = new ServicesCustomizationDocument;
666 g_test_services_customization_document->network_delay_ = base::TimeDelta();
670 void ServicesCustomizationDocument::ShutdownForTesting() {
671 delete g_test_services_customization_document;
672 g_test_services_customization_document = NULL;
675 } // namespace chromeos