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