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"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/files/file_path.h"
11 #include "base/json/json_reader.h"
12 #include "base/logging.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/metrics/histogram.h"
15 #include "base/prefs/pref_registry_simple.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/time/time.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/chromeos/login/wizard_controller.h"
24 #include "chrome/browser/chromeos/net/delay_network_call.h"
25 #include "chrome/browser/extensions/external_loader.h"
26 #include "chrome/browser/extensions/external_provider_impl.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
29 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
30 #include "chrome/common/extensions/extension_constants.h"
31 #include "chromeos/network/network_state.h"
32 #include "chromeos/network/network_state_handler.h"
33 #include "chromeos/system/statistics_provider.h"
34 #include "components/user_prefs/pref_registry_syncable.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "net/base/load_flags.h"
37 #include "net/http/http_response_headers.h"
38 #include "net/http/http_status_code.h"
39 #include "net/url_request/url_fetcher.h"
41 using content::BrowserThread
;
46 // Manifest attributes names.
47 const char kVersionAttr
[] = "version";
48 const char kDefaultAttr
[] = "default";
49 const char kInitialLocaleAttr
[] = "initial_locale";
50 const char kInitialTimezoneAttr
[] = "initial_timezone";
51 const char kKeyboardLayoutAttr
[] = "keyboard_layout";
52 const char kHwidMapAttr
[] = "hwid_map";
53 const char kHwidMaskAttr
[] = "hwid_mask";
54 const char kSetupContentAttr
[] = "setup_content";
55 const char kEulaPageAttr
[] = "eula_page";
56 const char kDefaultWallpaperAttr
[] = "default_wallpaper";
57 const char kDefaultAppsAttr
[] = "default_apps";
58 const char kLocalizedContent
[] = "localized_content";
59 const char kDefaultAppsFolderName
[] = "default_apps_folder_name";
61 const char kAcceptedManifestVersion
[] = "1.0";
63 // Path to OEM partner startup customization manifest.
64 const char kStartupCustomizationManifestPath
[] =
65 "/opt/oem/etc/startup_manifest.json";
67 // Name of local state option that tracks if services customization has been
69 const char kServicesCustomizationAppliedPref
[] = "ServicesCustomizationApplied";
71 // Maximum number of retries to fetch file if network is not available.
72 const int kMaxFetchRetries
= 3;
74 // Delay between file fetch retries if network is not available.
75 const int kRetriesDelayInSec
= 2;
77 // Name of profile option that tracks cached version of service customization.
78 const char kServicesCustomizationKey
[] = "customization.manifest_cache";
80 // Empty customization document that doesn't customize anything.
81 const char kEmptyServicesCustomizationManifest
[] = "{ \"version\": \"1.0\" }";
83 // Global overrider for ServicesCustomizationDocument for tests.
84 ServicesCustomizationDocument
* g_test_services_customization_document
= NULL
;
86 // Services customization document load results reported via the
87 // "ServicesCustomization.LoadResult" histogram.
88 // It is append-only enum due to use in a histogram!
89 enum HistogramServicesCustomizationLoadResult
{
90 HISTOGRAM_LOAD_RESULT_SUCCESS
= 0,
91 HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND
= 1,
92 HISTOGRAM_LOAD_RESULT_PARSING_ERROR
= 2,
93 HISTOGRAM_LOAD_RESULT_RETRIES_FAIL
= 3,
94 HISTOGRAM_LOAD_RESULT_MAX_VALUE
= 4
97 void LogManifestLoadResult(HistogramServicesCustomizationLoadResult result
) {
98 UMA_HISTOGRAM_ENUMERATION("ServicesCustomization.LoadResult",
100 HISTOGRAM_LOAD_RESULT_MAX_VALUE
);
103 std::string
GetLocaleSpecificStringImpl(
104 const base::DictionaryValue
* root
,
105 const std::string
& locale
,
106 const std::string
& dictionary_name
,
107 const std::string
& entry_name
) {
108 const base::DictionaryValue
* dictionary_content
= NULL
;
109 if (!root
|| !root
->GetDictionary(dictionary_name
, &dictionary_content
))
110 return std::string();
112 const base::DictionaryValue
* locale_dictionary
= NULL
;
113 if (dictionary_content
->GetDictionary(locale
, &locale_dictionary
)) {
115 if (locale_dictionary
->GetString(entry_name
, &result
))
119 const base::DictionaryValue
* default_dictionary
= NULL
;
120 if (dictionary_content
->GetDictionary(kDefaultAttr
, &default_dictionary
)) {
122 if (default_dictionary
->GetString(entry_name
, &result
))
126 return std::string();
129 } // anonymous namespace
131 // Template URL where to fetch OEM services customization manifest from.
132 const char ServicesCustomizationDocument::kManifestUrl
[] =
133 "https://ssl.gstatic.com/chrome/chromeos-customization/%s.json";
135 // A custom extensions::ExternalLoader that the ServicesCustomizationDocument
136 // creates and uses to publish OEM default apps to the extensions system.
137 class ServicesCustomizationExternalLoader
138 : public extensions::ExternalLoader
,
139 public base::SupportsWeakPtr
<ServicesCustomizationExternalLoader
> {
141 explicit ServicesCustomizationExternalLoader(Profile
* profile
)
142 : is_apps_set_(false), profile_(profile
) {}
144 Profile
* profile() { return profile_
; }
146 // Used by the ServicesCustomizationDocument to update the current apps.
147 void SetCurrentApps(scoped_ptr
<base::DictionaryValue
> prefs
) {
148 apps_
.Swap(prefs
.get());
153 // Implementation of extensions::ExternalLoader:
154 virtual void StartLoading() OVERRIDE
{
156 ServicesCustomizationDocument::GetInstance()->StartFetching();
157 // No return here to call LoadFinished with empty list initially.
158 // When manifest is fetched, it will be called again with real list.
159 // It is safe to return empty list because this provider didn't install
160 // any app yet so no app can be removed due to returning empty list.
163 prefs_
.reset(apps_
.DeepCopy());
164 VLOG(1) << "ServicesCustomization extension loader publishing "
165 << apps_
.size() << " apps.";
170 virtual ~ServicesCustomizationExternalLoader() {}
174 base::DictionaryValue apps_
;
177 DISALLOW_COPY_AND_ASSIGN(ServicesCustomizationExternalLoader
);
180 // CustomizationDocument implementation. ---------------------------------------
182 CustomizationDocument::CustomizationDocument(
183 const std::string
& accepted_version
)
184 : accepted_version_(accepted_version
) {}
186 CustomizationDocument::~CustomizationDocument() {}
188 bool CustomizationDocument::LoadManifestFromFile(
189 const base::FilePath
& manifest_path
) {
190 std::string manifest
;
191 if (!base::ReadFileToString(manifest_path
, &manifest
))
193 return LoadManifestFromString(manifest
);
196 bool CustomizationDocument::LoadManifestFromString(
197 const std::string
& manifest
) {
200 scoped_ptr
<base::Value
> root(base::JSONReader::ReadAndReturnError(manifest
,
201 base::JSON_ALLOW_TRAILING_COMMAS
, &error_code
, &error
));
202 if (error_code
!= base::JSONReader::JSON_NO_ERROR
)
204 DCHECK(root
.get() != NULL
);
205 if (root
.get() == NULL
)
207 DCHECK(root
->GetType() == base::Value::TYPE_DICTIONARY
);
208 if (root
->GetType() == base::Value::TYPE_DICTIONARY
) {
209 root_
.reset(static_cast<base::DictionaryValue
*>(root
.release()));
211 if (root_
->GetString(kVersionAttr
, &result
) &&
212 result
== accepted_version_
)
215 LOG(ERROR
) << "Wrong customization manifest version";
221 std::string
CustomizationDocument::GetLocaleSpecificString(
222 const std::string
& locale
,
223 const std::string
& dictionary_name
,
224 const std::string
& entry_name
) const {
225 return GetLocaleSpecificStringImpl(
226 root_
.get(), locale
, dictionary_name
, entry_name
);
229 // StartupCustomizationDocument implementation. --------------------------------
231 StartupCustomizationDocument::StartupCustomizationDocument()
232 : CustomizationDocument(kAcceptedManifestVersion
) {
234 // Loading manifest causes us to do blocking IO on UI thread.
235 // Temporarily allow it until we fix http://crosbug.com/11103
236 base::ThreadRestrictions::ScopedAllowIO allow_io
;
237 LoadManifestFromFile(base::FilePath(kStartupCustomizationManifestPath
));
239 Init(system::StatisticsProvider::GetInstance());
242 StartupCustomizationDocument::StartupCustomizationDocument(
243 system::StatisticsProvider
* statistics_provider
,
244 const std::string
& manifest
)
245 : CustomizationDocument(kAcceptedManifestVersion
) {
246 LoadManifestFromString(manifest
);
247 Init(statistics_provider
);
250 StartupCustomizationDocument::~StartupCustomizationDocument() {}
252 StartupCustomizationDocument
* StartupCustomizationDocument::GetInstance() {
253 return Singleton
<StartupCustomizationDocument
,
254 DefaultSingletonTraits
<StartupCustomizationDocument
> >::get();
257 void StartupCustomizationDocument::Init(
258 system::StatisticsProvider
* statistics_provider
) {
260 root_
->GetString(kInitialLocaleAttr
, &initial_locale_
);
261 root_
->GetString(kInitialTimezoneAttr
, &initial_timezone_
);
262 root_
->GetString(kKeyboardLayoutAttr
, &keyboard_layout_
);
265 if (statistics_provider
->GetMachineStatistic(
266 system::kHardwareClassKey
, &hwid
)) {
267 base::ListValue
* hwid_list
= NULL
;
268 if (root_
->GetList(kHwidMapAttr
, &hwid_list
)) {
269 for (size_t i
= 0; i
< hwid_list
->GetSize(); ++i
) {
270 base::DictionaryValue
* hwid_dictionary
= NULL
;
271 std::string hwid_mask
;
272 if (hwid_list
->GetDictionary(i
, &hwid_dictionary
) &&
273 hwid_dictionary
->GetString(kHwidMaskAttr
, &hwid_mask
)) {
274 if (MatchPattern(hwid
, hwid_mask
)) {
275 // If HWID for this machine matches some mask, use HWID specific
278 if (hwid_dictionary
->GetString(kInitialLocaleAttr
, &result
))
279 initial_locale_
= result
;
281 if (hwid_dictionary
->GetString(kInitialTimezoneAttr
, &result
))
282 initial_timezone_
= result
;
284 if (hwid_dictionary
->GetString(kKeyboardLayoutAttr
, &result
))
285 keyboard_layout_
= result
;
287 // Don't break here to allow other entires to be applied if match.
289 LOG(ERROR
) << "Syntax error in customization manifest";
294 LOG(ERROR
) << "HWID is missing in machine statistics";
298 // If manifest doesn't exist still apply values from VPD.
299 statistics_provider
->GetMachineStatistic(kInitialLocaleAttr
,
301 statistics_provider
->GetMachineStatistic(kInitialTimezoneAttr
,
303 statistics_provider
->GetMachineStatistic(kKeyboardLayoutAttr
,
305 configured_locales_
.resize(0);
306 base::SplitString(initial_locale_
, ',', &configured_locales_
);
307 // Let's always have configured_locales_.front() a valid entry.
308 if (configured_locales_
.size() == 0)
309 configured_locales_
.push_back(std::string());
312 const std::vector
<std::string
>&
313 StartupCustomizationDocument::configured_locales() const {
314 return configured_locales_
;
317 const std::string
& StartupCustomizationDocument::initial_locale_default()
319 DCHECK(configured_locales_
.size() > 0);
320 return configured_locales_
.front();
323 std::string
StartupCustomizationDocument::GetEULAPage(
324 const std::string
& locale
) const {
325 return GetLocaleSpecificString(locale
, kSetupContentAttr
, kEulaPageAttr
);
328 // ServicesCustomizationDocument implementation. -------------------------------
330 ServicesCustomizationDocument::ServicesCustomizationDocument()
331 : CustomizationDocument(kAcceptedManifestVersion
),
333 fetch_started_(false),
334 network_delay_(base::TimeDelta::FromMilliseconds(
335 kDefaultNetworkRetryDelayMS
)),
336 weak_ptr_factory_(this) {
339 ServicesCustomizationDocument::ServicesCustomizationDocument(
340 const std::string
& manifest
)
341 : CustomizationDocument(kAcceptedManifestVersion
),
342 network_delay_(base::TimeDelta::FromMilliseconds(
343 kDefaultNetworkRetryDelayMS
)),
344 weak_ptr_factory_(this) {
345 LoadManifestFromString(manifest
);
348 ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
351 ServicesCustomizationDocument
* ServicesCustomizationDocument::GetInstance() {
352 if (g_test_services_customization_document
)
353 return g_test_services_customization_document
;
355 return Singleton
<ServicesCustomizationDocument
,
356 DefaultSingletonTraits
<ServicesCustomizationDocument
> >::get();
360 void ServicesCustomizationDocument::RegisterPrefs(
361 PrefRegistrySimple
* registry
) {
362 registry
->RegisterBooleanPref(kServicesCustomizationAppliedPref
, false);
366 void ServicesCustomizationDocument::RegisterProfilePrefs(
367 user_prefs::PrefRegistrySyncable
* registry
) {
368 registry
->RegisterDictionaryPref(
369 kServicesCustomizationKey
,
370 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
374 bool ServicesCustomizationDocument::WasOOBECustomizationApplied() {
375 PrefService
* prefs
= g_browser_process
->local_state();
376 return prefs
->GetBoolean(kServicesCustomizationAppliedPref
);
380 void ServicesCustomizationDocument::SetApplied(bool val
) {
381 PrefService
* prefs
= g_browser_process
->local_state();
382 prefs
->SetBoolean(kServicesCustomizationAppliedPref
, val
);
385 void ServicesCustomizationDocument::StartFetching() {
386 if (IsReady() || fetch_started_
)
389 if (!url_
.is_valid()) {
390 std::string customization_id
;
391 chromeos::system::StatisticsProvider
* provider
=
392 chromeos::system::StatisticsProvider::GetInstance();
393 if (provider
->GetMachineStatistic(system::kCustomizationIdKey
,
394 &customization_id
) &&
395 !customization_id
.empty()) {
396 url_
= GURL(base::StringPrintf(
397 kManifestUrl
, StringToLowerASCII(customization_id
).c_str()));
401 if (url_
.is_valid()) {
402 fetch_started_
= true;
403 if (url_
.SchemeIsFile()) {
404 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
405 base::Bind(&ServicesCustomizationDocument::ReadFileInBackground
,
406 weak_ptr_factory_
.GetWeakPtr(),
407 base::FilePath(url_
.path())));
415 void ServicesCustomizationDocument::ReadFileInBackground(
416 base::WeakPtr
<ServicesCustomizationDocument
> self
,
417 const base::FilePath
& file
) {
418 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
420 std::string manifest
;
421 if (!base::ReadFileToString(file
, &manifest
)) {
423 LOG(ERROR
) << "Failed to load services customization manifest from: "
427 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
428 base::Bind(&ServicesCustomizationDocument::OnManifesteRead
,
433 void ServicesCustomizationDocument::OnManifesteRead(
434 const std::string
& manifest
) {
435 if (!manifest
.empty())
436 LoadManifestFromString(manifest
);
438 fetch_started_
= false;
441 void ServicesCustomizationDocument::StartFileFetch() {
442 DelayNetworkCall(base::Bind(&ServicesCustomizationDocument::DoStartFileFetch
,
443 weak_ptr_factory_
.GetWeakPtr()),
447 void ServicesCustomizationDocument::DoStartFileFetch() {
448 url_fetcher_
.reset(net::URLFetcher::Create(
449 url_
, net::URLFetcher::GET
, this));
450 url_fetcher_
->SetRequestContext(g_browser_process
->system_request_context());
451 url_fetcher_
->AddExtraRequestHeader("Accept: application/json");
452 url_fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
453 net::LOAD_DO_NOT_SAVE_COOKIES
|
454 net::LOAD_DISABLE_CACHE
|
455 net::LOAD_DO_NOT_SEND_AUTH_DATA
);
456 url_fetcher_
->Start();
459 bool ServicesCustomizationDocument::LoadManifestFromString(
460 const std::string
& manifest
) {
461 if (CustomizationDocument::LoadManifestFromString(manifest
)) {
462 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_SUCCESS
);
467 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_PARSING_ERROR
);
471 void ServicesCustomizationDocument::OnManifestLoaded() {
472 if (!ServicesCustomizationDocument::WasOOBECustomizationApplied())
473 ApplyOOBECustomization();
475 scoped_ptr
<base::DictionaryValue
> prefs
=
476 GetDefaultAppsInProviderFormat(*root_
);
477 for (ExternalLoaders::iterator it
= external_loaders_
.begin();
478 it
!= external_loaders_
.end(); ++it
) {
480 UpdateCachedManifest((*it
)->profile());
481 (*it
)->SetCurrentApps(
482 scoped_ptr
<base::DictionaryValue
>(prefs
->DeepCopy()));
483 SetOemFolderName((*it
)->profile(), *root_
);
488 void ServicesCustomizationDocument::OnURLFetchComplete(
489 const net::URLFetcher
* source
) {
490 std::string mime_type
;
492 if (source
->GetStatus().is_success() &&
493 source
->GetResponseCode() == net::HTTP_OK
&&
494 source
->GetResponseHeaders()->GetMimeType(&mime_type
) &&
495 mime_type
== "application/json" &&
496 source
->GetResponseAsString(&data
)) {
497 LoadManifestFromString(data
);
498 } else if (source
->GetResponseCode() == net::HTTP_NOT_FOUND
) {
499 LOG(ERROR
) << "Customization manifest is missing on server: "
500 << source
->GetURL().spec();
501 OnCustomizationNotFound();
503 if (num_retries_
< kMaxFetchRetries
) {
505 content::BrowserThread::PostDelayedTask(
506 content::BrowserThread::UI
,
508 base::Bind(&ServicesCustomizationDocument::StartFileFetch
,
509 weak_ptr_factory_
.GetWeakPtr()),
510 base::TimeDelta::FromSeconds(kRetriesDelayInSec
));
513 // This doesn't stop fetching manifest on next restart.
514 LOG(ERROR
) << "URL fetch for services customization failed:"
515 << " response code = " << source
->GetResponseCode()
516 << " URL = " << source
->GetURL().spec();
518 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_RETRIES_FAIL
);
520 fetch_started_
= false;
523 bool ServicesCustomizationDocument::ApplyOOBECustomization() {
524 // TODO(dpolukhin): apply default wallpaper, crbug.com/348136.
529 GURL
ServicesCustomizationDocument::GetDefaultWallpaperUrl() const {
534 root_
->GetString(kDefaultWallpaperAttr
, &url
);
538 bool ServicesCustomizationDocument::GetDefaultApps(
539 std::vector
<std::string
>* ids
) const {
544 base::ListValue
* apps_list
= NULL
;
545 if (!root_
->GetList(kDefaultAppsAttr
, &apps_list
))
548 for (size_t i
= 0; i
< apps_list
->GetSize(); ++i
) {
550 if (apps_list
->GetString(i
, &app_id
)) {
551 ids
->push_back(app_id
);
553 LOG(ERROR
) << "Wrong format of default application list";
561 std::string
ServicesCustomizationDocument::GetOemAppsFolderName(
562 const std::string
& locale
) const {
564 return std::string();
566 return GetOemAppsFolderNameImpl(locale
, *root_
);
569 scoped_ptr
<base::DictionaryValue
>
570 ServicesCustomizationDocument::GetDefaultAppsInProviderFormat(
571 const base::DictionaryValue
& root
) {
572 scoped_ptr
<base::DictionaryValue
> prefs(new base::DictionaryValue
);
573 const base::ListValue
* apps_list
= NULL
;
574 if (root
.GetList(kDefaultAppsAttr
, &apps_list
)) {
575 for (size_t i
= 0; i
< apps_list
->GetSize(); ++i
) {
577 if (apps_list
->GetString(i
, &app_id
)) {
578 base::DictionaryValue
* entry
= new base::DictionaryValue
;
579 entry
->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl
,
580 extension_urls::GetWebstoreUpdateUrl().spec());
581 prefs
->Set(app_id
, entry
);
583 LOG(ERROR
) << "Wrong format of default application list";
593 void ServicesCustomizationDocument::UpdateCachedManifest(Profile
* profile
) {
594 profile
->GetPrefs()->Set(kServicesCustomizationKey
, *root_
);
597 extensions::ExternalLoader
* ServicesCustomizationDocument::CreateExternalLoader(
599 ServicesCustomizationExternalLoader
* loader
=
600 new ServicesCustomizationExternalLoader(profile
);
601 external_loaders_
.push_back(loader
->AsWeakPtr());
604 UpdateCachedManifest(profile
);
605 loader
->SetCurrentApps(GetDefaultAppsInProviderFormat(*root_
));
606 SetOemFolderName(profile
, *root_
);
608 const base::DictionaryValue
* root
=
609 profile
->GetPrefs()->GetDictionary(kServicesCustomizationKey
);
611 if (root
&& root
->GetString(kVersionAttr
, &version
)) {
612 // If version exists, profile has cached version of customization.
613 loader
->SetCurrentApps(GetDefaultAppsInProviderFormat(*root
));
614 SetOemFolderName(profile
, *root
);
616 // StartFetching will be called from ServicesCustomizationExternalLoader
617 // when StartLoading is called. We can't initiate manifest fetch here
618 // because caller may never call StartLoading for the provider.
625 void ServicesCustomizationDocument::OnCustomizationNotFound() {
626 LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND
);
627 LoadManifestFromString(kEmptyServicesCustomizationManifest
);
630 void ServicesCustomizationDocument::SetOemFolderName(
632 const base::DictionaryValue
& root
) {
633 std::string locale
= g_browser_process
->GetApplicationLocale();
634 std::string name
= GetOemAppsFolderNameImpl(locale
, root
);
636 app_list::AppListSyncableService
* service
=
637 app_list::AppListSyncableServiceFactory::GetForProfile(profile
);
639 LOG(WARNING
) << "AppListSyncableService is not ready for setting OEM "
643 service
->SetOemFolderName(name
);
647 std::string
ServicesCustomizationDocument::GetOemAppsFolderNameImpl(
648 const std::string
& locale
,
649 const base::DictionaryValue
& root
) const {
650 return GetLocaleSpecificStringImpl(
651 &root
, locale
, kLocalizedContent
, kDefaultAppsFolderName
);
655 void ServicesCustomizationDocument::InitializeForTesting() {
656 g_test_services_customization_document
= new ServicesCustomizationDocument
;
657 g_test_services_customization_document
->network_delay_
= base::TimeDelta();
661 void ServicesCustomizationDocument::ShutdownForTesting() {
662 delete g_test_services_customization_document
;
663 g_test_services_customization_document
= NULL
;
666 } // namespace chromeos