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/prefs/pref_registry_simple.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/chromeos/login/wizard_controller.h"
23 #include "chrome/browser/extensions/external_loader.h"
24 #include "chrome/browser/extensions/external_provider_impl.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "chromeos/network/network_state.h"
28 #include "chromeos/network/network_state_handler.h"
29 #include "chromeos/system/statistics_provider.h"
30 #include "components/user_prefs/pref_registry_syncable.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "net/base/load_flags.h"
33 #include "net/http/http_response_headers.h"
34 #include "net/url_request/url_fetcher.h"
36 using content::BrowserThread
;
38 // Manifest attributes names.
42 const char kVersionAttr
[] = "version";
43 const char kDefaultAttr
[] = "default";
44 const char kInitialLocaleAttr
[] = "initial_locale";
45 const char kInitialTimezoneAttr
[] = "initial_timezone";
46 const char kKeyboardLayoutAttr
[] = "keyboard_layout";
47 const char kHwidMapAttr
[] = "hwid_map";
48 const char kHwidMaskAttr
[] = "hwid_mask";
49 const char kSetupContentAttr
[] = "setup_content";
50 const char kEulaPageAttr
[] = "eula_page";
51 const char kDefaultWallpaperAttr
[] = "default_wallpaper";
52 const char kDefaultAppsAttr
[] = "default_apps";
54 const char kAcceptedManifestVersion
[] = "1.0";
56 // Path to OEM partner startup customization manifest.
57 const char kStartupCustomizationManifestPath
[] =
58 "/opt/oem/etc/startup_manifest.json";
60 // Name of local state option that tracks if services customization has been
62 const char kServicesCustomizationAppliedPref
[] = "ServicesCustomizationApplied";
64 // Maximum number of retries to fetch file if network is not available.
65 const int kMaxFetchRetries
= 3;
67 // Delay between file fetch retries if network is not available.
68 const int kRetriesDelayInSec
= 2;
70 // Name of profile option that tracks cached version of service customization.
71 const char kServicesCustomizationKey
[] = "customization.manifest_cache";
73 } // anonymous namespace
77 // Template URL where to fetch OEM services customization manifest from.
78 const char ServicesCustomizationDocument::kManifestUrl
[] =
79 "https://ssl.gstatic.com/chrome/chromeos-customization/%s.json";
81 // A custom extensions::ExternalLoader that the ServicesCustomizationDocument
82 // creates and uses to publish OEM default apps to the extensions system.
83 class ServicesCustomizationExternalLoader
84 : public extensions::ExternalLoader
,
85 public base::SupportsWeakPtr
<ServicesCustomizationExternalLoader
> {
87 explicit ServicesCustomizationExternalLoader(Profile
* profile
)
88 : is_apps_set_(false), profile_(profile
) {}
90 Profile
* profile() { return profile_
; }
92 // Used by the ServicesCustomizationDocument to update the current apps.
93 void SetCurrentApps(scoped_ptr
<base::DictionaryValue
> prefs
) {
94 apps_
.Swap(prefs
.get());
99 // Implementation of extensions::ExternalLoader:
100 virtual void StartLoading() OVERRIDE
{
102 ServicesCustomizationDocument::GetInstance()->StartFetching();
103 // No return here to call LoadFinished with empty list initially.
104 // When manifest is fetched, it will be called again with real list.
105 // It is safe to return empty list because this provider didn't install
106 // any app yet so no app can be removed due to returning empty list.
109 prefs_
.reset(apps_
.DeepCopy());
110 VLOG(1) << "ServicesCustomization extension loader publishing "
111 << apps_
.size() << " apps.";
116 virtual ~ServicesCustomizationExternalLoader() {}
120 base::DictionaryValue apps_
;
123 DISALLOW_COPY_AND_ASSIGN(ServicesCustomizationExternalLoader
);
126 // CustomizationDocument implementation. ---------------------------------------
128 CustomizationDocument::CustomizationDocument(
129 const std::string
& accepted_version
)
130 : accepted_version_(accepted_version
) {}
132 CustomizationDocument::~CustomizationDocument() {}
134 bool CustomizationDocument::LoadManifestFromFile(
135 const base::FilePath
& manifest_path
) {
136 std::string manifest
;
137 if (!base::ReadFileToString(manifest_path
, &manifest
))
139 return LoadManifestFromString(manifest
);
142 bool CustomizationDocument::LoadManifestFromString(
143 const std::string
& manifest
) {
146 scoped_ptr
<base::Value
> root(base::JSONReader::ReadAndReturnError(manifest
,
147 base::JSON_ALLOW_TRAILING_COMMAS
, &error_code
, &error
));
148 if (error_code
!= base::JSONReader::JSON_NO_ERROR
)
150 DCHECK(root
.get() != NULL
);
151 if (root
.get() == NULL
)
153 DCHECK(root
->GetType() == base::Value::TYPE_DICTIONARY
);
154 if (root
->GetType() == base::Value::TYPE_DICTIONARY
) {
155 root_
.reset(static_cast<base::DictionaryValue
*>(root
.release()));
157 if (root_
->GetString(kVersionAttr
, &result
) &&
158 result
== accepted_version_
)
161 LOG(ERROR
) << "Wrong customization manifest version";
167 std::string
CustomizationDocument::GetLocaleSpecificString(
168 const std::string
& locale
,
169 const std::string
& dictionary_name
,
170 const std::string
& entry_name
) const {
171 base::DictionaryValue
* dictionary_content
= NULL
;
173 !root_
->GetDictionary(dictionary_name
, &dictionary_content
))
174 return std::string();
176 base::DictionaryValue
* locale_dictionary
= NULL
;
177 if (dictionary_content
->GetDictionary(locale
, &locale_dictionary
)) {
179 if (locale_dictionary
->GetString(entry_name
, &result
))
183 base::DictionaryValue
* default_dictionary
= NULL
;
184 if (dictionary_content
->GetDictionary(kDefaultAttr
, &default_dictionary
)) {
186 if (default_dictionary
->GetString(entry_name
, &result
))
190 return std::string();
193 // StartupCustomizationDocument implementation. --------------------------------
195 StartupCustomizationDocument::StartupCustomizationDocument()
196 : CustomizationDocument(kAcceptedManifestVersion
) {
198 // Loading manifest causes us to do blocking IO on UI thread.
199 // Temporarily allow it until we fix http://crosbug.com/11103
200 base::ThreadRestrictions::ScopedAllowIO allow_io
;
201 LoadManifestFromFile(base::FilePath(kStartupCustomizationManifestPath
));
203 Init(system::StatisticsProvider::GetInstance());
206 StartupCustomizationDocument::StartupCustomizationDocument(
207 system::StatisticsProvider
* statistics_provider
,
208 const std::string
& manifest
)
209 : CustomizationDocument(kAcceptedManifestVersion
) {
210 LoadManifestFromString(manifest
);
211 Init(statistics_provider
);
214 StartupCustomizationDocument::~StartupCustomizationDocument() {}
216 StartupCustomizationDocument
* StartupCustomizationDocument::GetInstance() {
217 return Singleton
<StartupCustomizationDocument
,
218 DefaultSingletonTraits
<StartupCustomizationDocument
> >::get();
221 void StartupCustomizationDocument::Init(
222 system::StatisticsProvider
* statistics_provider
) {
224 root_
->GetString(kInitialLocaleAttr
, &initial_locale_
);
225 root_
->GetString(kInitialTimezoneAttr
, &initial_timezone_
);
226 root_
->GetString(kKeyboardLayoutAttr
, &keyboard_layout_
);
229 if (statistics_provider
->GetMachineStatistic(
230 system::kHardwareClassKey
, &hwid
)) {
231 base::ListValue
* hwid_list
= NULL
;
232 if (root_
->GetList(kHwidMapAttr
, &hwid_list
)) {
233 for (size_t i
= 0; i
< hwid_list
->GetSize(); ++i
) {
234 base::DictionaryValue
* hwid_dictionary
= NULL
;
235 std::string hwid_mask
;
236 if (hwid_list
->GetDictionary(i
, &hwid_dictionary
) &&
237 hwid_dictionary
->GetString(kHwidMaskAttr
, &hwid_mask
)) {
238 if (MatchPattern(hwid
, hwid_mask
)) {
239 // If HWID for this machine matches some mask, use HWID specific
242 if (hwid_dictionary
->GetString(kInitialLocaleAttr
, &result
))
243 initial_locale_
= result
;
245 if (hwid_dictionary
->GetString(kInitialTimezoneAttr
, &result
))
246 initial_timezone_
= result
;
248 if (hwid_dictionary
->GetString(kKeyboardLayoutAttr
, &result
))
249 keyboard_layout_
= result
;
251 // Don't break here to allow other entires to be applied if match.
253 LOG(ERROR
) << "Syntax error in customization manifest";
258 LOG(ERROR
) << "HWID is missing in machine statistics";
262 // If manifest doesn't exist still apply values from VPD.
263 statistics_provider
->GetMachineStatistic(kInitialLocaleAttr
,
265 statistics_provider
->GetMachineStatistic(kInitialTimezoneAttr
,
267 statistics_provider
->GetMachineStatistic(kKeyboardLayoutAttr
,
269 configured_locales_
.resize(0);
270 base::SplitString(initial_locale_
, ',', &configured_locales_
);
271 // Let's always have configured_locales_.front() a valid entry.
272 if (configured_locales_
.size() == 0)
273 configured_locales_
.push_back(std::string());
276 const std::vector
<std::string
>&
277 StartupCustomizationDocument::configured_locales() const {
278 return configured_locales_
;
281 const std::string
& StartupCustomizationDocument::initial_locale_default()
283 DCHECK(configured_locales_
.size() > 0);
284 return configured_locales_
.front();
287 std::string
StartupCustomizationDocument::GetEULAPage(
288 const std::string
& locale
) const {
289 return GetLocaleSpecificString(locale
, kSetupContentAttr
, kEulaPageAttr
);
292 // ServicesCustomizationDocument implementation. -------------------------------
294 ServicesCustomizationDocument::ServicesCustomizationDocument()
295 : CustomizationDocument(kAcceptedManifestVersion
),
297 fetch_started_(false) {
300 ServicesCustomizationDocument::ServicesCustomizationDocument(
301 const std::string
& manifest
)
302 : CustomizationDocument(kAcceptedManifestVersion
) {
303 LoadManifestFromString(manifest
);
306 ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
309 ServicesCustomizationDocument
* ServicesCustomizationDocument::GetInstance() {
310 return Singleton
<ServicesCustomizationDocument
,
311 DefaultSingletonTraits
<ServicesCustomizationDocument
> >::get();
315 void ServicesCustomizationDocument::RegisterPrefs(
316 PrefRegistrySimple
* registry
) {
317 registry
->RegisterBooleanPref(kServicesCustomizationAppliedPref
, false);
321 void ServicesCustomizationDocument::RegisterProfilePrefs(
322 user_prefs::PrefRegistrySyncable
* registry
) {
323 registry
->RegisterDictionaryPref(
324 kServicesCustomizationKey
,
325 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
329 bool ServicesCustomizationDocument::WasOOBECustomizationApplied() {
330 PrefService
* prefs
= g_browser_process
->local_state();
331 return prefs
->GetBoolean(kServicesCustomizationAppliedPref
);
335 void ServicesCustomizationDocument::SetApplied(bool val
) {
336 PrefService
* prefs
= g_browser_process
->local_state();
337 prefs
->SetBoolean(kServicesCustomizationAppliedPref
, val
);
340 void ServicesCustomizationDocument::StartFetching() {
341 if (!fetch_started_
) {
342 if (!url_
.is_valid()) {
343 std::string customization_id
;
344 chromeos::system::StatisticsProvider
* provider
=
345 chromeos::system::StatisticsProvider::GetInstance();
346 if (provider
->GetMachineStatistic(system::kCustomizationIdKey
,
347 &customization_id
) &&
348 !customization_id
.empty()) {
349 url_
= GURL(base::StringPrintf(
350 kManifestUrl
, StringToLowerASCII(customization_id
).c_str()));
354 if (url_
.is_valid()) {
355 fetch_started_
= true;
356 if (url_
.SchemeIsFile()) {
357 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
358 base::Bind(&ServicesCustomizationDocument::ReadFileInBackground
,
359 base::Unretained(this), // this class is a singleton.
360 base::FilePath(url_
.path())));
368 void ServicesCustomizationDocument::ReadFileInBackground(
369 const base::FilePath
& file
) {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
372 std::string manifest
;
373 if (!base::ReadFileToString(file
, &manifest
)) {
375 LOG(ERROR
) << "Failed to load services customization manifest from: "
379 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
380 base::Bind(&ServicesCustomizationDocument::OnManifesteRead
,
381 base::Unretained(this), // this class is a singleton.
385 void ServicesCustomizationDocument::OnManifesteRead(
386 const std::string
& manifest
) {
387 if (!manifest
.empty())
388 LoadManifestFromString(manifest
);
390 fetch_started_
= false;
393 void ServicesCustomizationDocument::StartFileFetch() {
394 url_fetcher_
.reset(net::URLFetcher::Create(
395 url_
, net::URLFetcher::GET
, this));
396 url_fetcher_
->SetRequestContext(g_browser_process
->system_request_context());
397 url_fetcher_
->AddExtraRequestHeader("Accept: application/json");
398 url_fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
399 net::LOAD_DO_NOT_SAVE_COOKIES
|
400 net::LOAD_DISABLE_CACHE
|
401 net::LOAD_DO_NOT_SEND_AUTH_DATA
);
402 url_fetcher_
->Start();
405 bool ServicesCustomizationDocument::LoadManifestFromString(
406 const std::string
& manifest
) {
407 if (CustomizationDocument::LoadManifestFromString(manifest
)) {
414 void ServicesCustomizationDocument::OnManifestLoaded() {
415 if (!ServicesCustomizationDocument::WasOOBECustomizationApplied())
416 ApplyOOBECustomization();
418 scoped_ptr
<base::DictionaryValue
> prefs
=
419 GetDefaultAppsInProviderFormat(*root_
);
420 for (ExternalLoaders::iterator it
= external_loaders_
.begin();
421 it
!= external_loaders_
.end(); ++it
) {
423 UpdateCachedManifest((*it
)->profile());
424 (*it
)->SetCurrentApps(
425 scoped_ptr
<base::DictionaryValue
>(prefs
->DeepCopy()));
430 void ServicesCustomizationDocument::OnURLFetchComplete(
431 const net::URLFetcher
* source
) {
432 std::string mime_type
;
434 if (source
->GetStatus().is_success() &&
435 source
->GetResponseCode() == 200 &&
436 source
->GetResponseHeaders()->GetMimeType(&mime_type
) &&
437 mime_type
== "application/json" &&
438 source
->GetResponseAsString(&data
)) {
439 LoadManifestFromString(data
);
440 fetch_started_
= false;
442 const NetworkState
* default_network
=
443 NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
444 // TODO(dpolukhin): wait for network connected state, crbug.com/343589.
445 if (default_network
&& default_network
->IsConnectedState() &&
446 num_retries_
< kMaxFetchRetries
) {
448 retry_timer_
.Start(FROM_HERE
,
449 base::TimeDelta::FromSeconds(kRetriesDelayInSec
),
450 this, &ServicesCustomizationDocument::StartFileFetch
);
453 LOG(ERROR
) << "URL fetch for services customization failed:"
454 << " response code = " << source
->GetResponseCode()
455 << " URL = " << source
->GetURL().spec();
456 fetch_started_
= false;
460 bool ServicesCustomizationDocument::ApplyOOBECustomization() {
461 // TODO(dpolukhin): apply default wallpaper.
466 GURL
ServicesCustomizationDocument::GetDefaultWallpaperUrl() const {
471 root_
->GetString(kDefaultWallpaperAttr
, &url
);
475 bool ServicesCustomizationDocument::GetDefaultApps(
476 std::vector
<std::string
>* ids
) const {
481 base::ListValue
* apps_list
= NULL
;
482 if (!root_
->GetList(kDefaultAppsAttr
, &apps_list
))
485 for (size_t i
= 0; i
< apps_list
->GetSize(); ++i
) {
487 if (apps_list
->GetString(i
, &app_id
)) {
488 ids
->push_back(app_id
);
490 LOG(ERROR
) << "Wrong format of default application list";
498 scoped_ptr
<base::DictionaryValue
>
499 ServicesCustomizationDocument::GetDefaultAppsInProviderFormat(
500 const base::DictionaryValue
& root
) {
501 scoped_ptr
<base::DictionaryValue
> prefs(new base::DictionaryValue
);
502 const base::ListValue
* apps_list
= NULL
;
503 if (root
.GetList(kDefaultAppsAttr
, &apps_list
)) {
504 for (size_t i
= 0; i
< apps_list
->GetSize(); ++i
) {
506 if (apps_list
->GetString(i
, &app_id
)) {
507 base::DictionaryValue
* entry
= new base::DictionaryValue
;
508 entry
->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl
,
509 extension_urls::GetWebstoreUpdateUrl().spec());
510 prefs
->Set(app_id
, entry
);
512 LOG(ERROR
) << "Wrong format of default application list";
522 void ServicesCustomizationDocument::UpdateCachedManifest(Profile
* profile
) {
523 profile
->GetPrefs()->Set(kServicesCustomizationKey
, *root_
);
526 extensions::ExternalLoader
* ServicesCustomizationDocument::CreateExternalLoader(
528 ServicesCustomizationExternalLoader
* loader
=
529 new ServicesCustomizationExternalLoader(profile
);
530 external_loaders_
.push_back(loader
->AsWeakPtr());
533 UpdateCachedManifest(profile
);
534 loader
->SetCurrentApps(GetDefaultAppsInProviderFormat(*root_
));
536 const base::DictionaryValue
* root
=
537 profile
->GetPrefs()->GetDictionary(kServicesCustomizationKey
);
539 if (root
&& root
->GetString(kVersionAttr
, &version
)) {
540 // If version exists, profile has cached version of customization.
541 loader
->SetCurrentApps(GetDefaultAppsInProviderFormat(*root
));
542 // TODO(dpolukhin): periodically refresh cached copy, crbug.com/343589.
544 // StartFetching will be called from ServicesCustomizationExternalLoader
545 // when StartLoading is called. We can't initiate manifest fetch here
546 // because caller may never call StartLoading for the provider.
553 } // namespace chromeos