| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/customization/customization_document.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/constants/ash_paths.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/i18n/rtl.h" |
| #include "base/json/json_reader.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/path_service.h" |
| #include "base/strings/pattern.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/customization/customization_wallpaper_downloader.h" |
| #include "chrome/browser/ash/customization/customization_wallpaper_util.h" |
| #include "chrome/browser/ash/login/wizard_controller.h" |
| #include "chrome/browser/ash/net/delay_network_call.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/extensions/default_app_order.h" |
| #include "chrome/browser/extensions/external_loader.h" |
| #include "chrome/browser/extensions/external_provider_impl.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/app_list/app_list_syncable_service.h" |
| #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/system/statistics_provider.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/common/extension_urls.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| |
| namespace ash { |
| namespace { |
| |
| // Manifest attributes names. |
| const char kVersionAttr[] = "version"; |
| const char kDefaultAttr[] = "default"; |
| const char kInitialLocaleAttr[] = "initial_locale"; |
| const char kInitialTimezoneAttr[] = "initial_timezone"; |
| const char kKeyboardLayoutAttr[] = "keyboard_layout"; |
| const char kHwidMapAttr[] = "hwid_map"; |
| const char kHwidMaskAttr[] = "hwid_mask"; |
| const char kSetupContentAttr[] = "setup_content"; |
| const char kEulaPageAttr[] = "eula_page"; |
| const char kDefaultWallpaperAttr[] = "default_wallpaper"; |
| const char kDefaultAppsAttr[] = "default_apps"; |
| const char kLocalizedContent[] = "localized_content"; |
| const char kDefaultAppsFolderName[] = "default_apps_folder_name"; |
| const char kIdAttr[] = "id"; |
| |
| const char kAcceptedManifestVersion[] = "1.0"; |
| |
| // This is subdirectory relative to PathService(DIR_CHROMEOS_CUSTOM_WALLPAPERS), |
| // where downloaded (and resized) wallpaper is stored. |
| const char kCustomizationDefaultWallpaperDir[] = "customization"; |
| |
| // The original downloaded image file is stored under this name. |
| const char kCustomizationDefaultWallpaperDownloadedFile[] = |
| "default_downloaded_wallpaper.bin"; |
| |
| // Name of local state option that tracks if services customization has been |
| // applied. |
| const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied"; |
| |
| // Maximum number of retries to fetch file if network is not available. |
| const int kMaxFetchRetries = 3; |
| |
| // Delay between file fetch retries if network is not available. |
| const int kRetriesDelayInSec = 2; |
| |
| // Name of profile option that tracks cached version of service customization. |
| const char kServicesCustomizationKey[] = "customization.manifest_cache"; |
| |
| // Empty customization document that doesn't customize anything. |
| const char kEmptyServicesCustomizationManifest[] = "{ \"version\": \"1.0\" }"; |
| |
| constexpr net::NetworkTrafficAnnotationTag kCustomizationDocumentNetworkTag = |
| net::DefineNetworkTrafficAnnotation("customization_document", |
| R"( |
| semantics { |
| sender: "Customization document" |
| description: |
| "Get OEM customization manifest from OEM specific URLs that " |
| "provide custom configuration locales, wallpaper etc." |
| trigger: |
| "Triggered on OOBE after user accepts EULA and everytime the " |
| "device boots in. Expected to run only once at OOBE. If the " |
| "network request fails, retried each boot until it succeeds." |
| data: "None." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "This feature is set by OEMs and can be overridden by users." |
| policy_exception_justification: |
| "This request is made based on OEM customization and does not " |
| "send/store any sensitive data." |
| })"); |
| |
| struct CustomizationDocumentTestOverride { |
| ServicesCustomizationDocument* customization_document = nullptr; |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory; |
| }; |
| |
| // Global overrider for ServicesCustomizationDocument for tests. |
| CustomizationDocumentTestOverride* g_test_overrides = nullptr; |
| |
| // Services customization document load results reported via the |
| // "ServicesCustomization.LoadResult" histogram. |
| // It is append-only enum due to use in a histogram! |
| enum HistogramServicesCustomizationLoadResult { |
| HISTOGRAM_LOAD_RESULT_SUCCESS = 0, |
| HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND = 1, |
| HISTOGRAM_LOAD_RESULT_PARSING_ERROR = 2, |
| HISTOGRAM_LOAD_RESULT_RETRIES_FAIL = 3, |
| HISTOGRAM_LOAD_RESULT_MAX_VALUE = 4 |
| }; |
| |
| void LogManifestLoadResult(HistogramServicesCustomizationLoadResult result) { |
| UMA_HISTOGRAM_ENUMERATION("ServicesCustomization.LoadResult", |
| result, |
| HISTOGRAM_LOAD_RESULT_MAX_VALUE); |
| } |
| |
| std::string GetLocaleSpecificStringImpl( |
| const base::DictionaryValue* root, |
| const std::string& locale, |
| const std::string& dictionary_name, |
| const std::string& entry_name) { |
| const base::DictionaryValue* dictionary_content = NULL; |
| if (!root || !root->GetDictionary(dictionary_name, &dictionary_content)) |
| return std::string(); |
| |
| const base::DictionaryValue* locale_dictionary = NULL; |
| if (dictionary_content->GetDictionary(locale, &locale_dictionary)) { |
| std::string result; |
| if (locale_dictionary->GetString(entry_name, &result)) |
| return result; |
| } |
| |
| const base::DictionaryValue* default_dictionary = NULL; |
| if (dictionary_content->GetDictionary(kDefaultAttr, &default_dictionary)) { |
| std::string result; |
| if (default_dictionary->GetString(entry_name, &result)) |
| return result; |
| } |
| |
| return std::string(); |
| } |
| |
| void CheckWallpaperCacheExists(const base::FilePath& path, bool* exists) { |
| DCHECK(exists); |
| *exists = base::PathExists(path); |
| } |
| |
| std::string ReadFileInBackground(const base::FilePath& file) { |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| |
| std::string manifest; |
| if (!base::ReadFileToString(file, &manifest)) { |
| manifest.clear(); |
| LOG(ERROR) << "Failed to load services customization manifest from: " |
| << file.value(); |
| } |
| |
| return manifest; |
| } |
| |
| } // anonymous namespace |
| |
| // Template URL where to fetch OEM services customization manifest from. |
| const char ServicesCustomizationDocument::kManifestUrl[] = |
| "https://ssl.gstatic.com/chrome/chromeos-customization/%s.json"; |
| |
| // A custom extensions::ExternalLoader that the ServicesCustomizationDocument |
| // creates and uses to publish OEM default apps to the extensions system. |
| class ServicesCustomizationExternalLoader |
| : public extensions::ExternalLoader, |
| public base::SupportsWeakPtr<ServicesCustomizationExternalLoader> { |
| public: |
| explicit ServicesCustomizationExternalLoader(Profile* profile) |
| : is_apps_set_(false), profile_(profile) {} |
| |
| ServicesCustomizationExternalLoader( |
| const ServicesCustomizationExternalLoader&) = delete; |
| ServicesCustomizationExternalLoader& operator=( |
| const ServicesCustomizationExternalLoader&) = delete; |
| |
| Profile* profile() { return profile_; } |
| |
| // Used by the ServicesCustomizationDocument to update the current apps. |
| void SetCurrentApps(std::unique_ptr<base::DictionaryValue> prefs) { |
| apps_.Swap(prefs.get()); |
| is_apps_set_ = true; |
| StartLoading(); |
| } |
| |
| // Implementation of extensions::ExternalLoader: |
| void StartLoading() override { |
| if (!is_apps_set_) { |
| ServicesCustomizationDocument::GetInstance()->StartFetching(); |
| // In case of missing customization ID, SetCurrentApps will be called |
| // synchronously from StartFetching and this function will be called |
| // recursively so we need to return to avoid calling LoadFinished twice. |
| // In case of async load it is safe to return empty list because this |
| // provider didn't install any app yet so no app can be removed due to |
| // returning empty list. |
| if (is_apps_set_) |
| return; |
| } |
| |
| VLOG(1) << "ServicesCustomization extension loader publishing " |
| << apps_.DictSize() << " apps."; |
| LoadFinished(apps_.CreateDeepCopy()); |
| } |
| |
| protected: |
| ~ServicesCustomizationExternalLoader() override {} |
| |
| private: |
| bool is_apps_set_; |
| base::DictionaryValue apps_; |
| Profile* profile_; |
| }; |
| |
| // CustomizationDocument implementation. --------------------------------------- |
| |
| CustomizationDocument::CustomizationDocument( |
| const std::string& accepted_version) |
| : accepted_version_(accepted_version) {} |
| |
| CustomizationDocument::~CustomizationDocument() {} |
| |
| bool CustomizationDocument::LoadManifestFromFile( |
| const base::FilePath& manifest_path) { |
| std::string manifest; |
| if (!base::ReadFileToString(manifest_path, &manifest)) |
| return false; |
| return LoadManifestFromString(manifest); |
| } |
| |
| bool CustomizationDocument::LoadManifestFromString( |
| const std::string& manifest) { |
| base::JSONReader::ValueWithError parsed_json = |
| base::JSONReader::ReadAndReturnValueWithError( |
| manifest, base::JSON_ALLOW_TRAILING_COMMAS | |
| base::JSON_PARSE_CHROMIUM_EXTENSIONS); |
| if (!parsed_json.value) { |
| LOG(ERROR) << parsed_json.error_message; |
| NOTREACHED(); |
| return false; |
| } |
| std::unique_ptr<base::Value> root = |
| base::Value::ToUniquePtrValue(std::move(*parsed_json.value)); |
| |
| root_ = base::DictionaryValue::From(std::move(root)); |
| if (!root_) { |
| NOTREACHED(); |
| return false; |
| } |
| |
| std::string result; |
| if (!root_->GetString(kVersionAttr, &result) || result != accepted_version_) { |
| LOG(ERROR) << "Wrong customization manifest version"; |
| root_.reset(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::string CustomizationDocument::GetLocaleSpecificString( |
| const std::string& locale, |
| const std::string& dictionary_name, |
| const std::string& entry_name) const { |
| return GetLocaleSpecificStringImpl( |
| root_.get(), locale, dictionary_name, entry_name); |
| } |
| |
| // StartupCustomizationDocument implementation. -------------------------------- |
| |
| StartupCustomizationDocument::StartupCustomizationDocument() |
| : CustomizationDocument(kAcceptedManifestVersion) { |
| { |
| // Loading manifest causes us to do blocking IO on UI thread. |
| // Temporarily allow it until we fix http://crosbug.com/11103 |
| base::ThreadRestrictions::ScopedAllowIO allow_io; |
| base::FilePath startup_customization_manifest; |
| base::PathService::Get(chromeos::FILE_STARTUP_CUSTOMIZATION_MANIFEST, |
| &startup_customization_manifest); |
| LoadManifestFromFile(startup_customization_manifest); |
| } |
| Init(chromeos::system::StatisticsProvider::GetInstance()); |
| } |
| |
| StartupCustomizationDocument::StartupCustomizationDocument( |
| chromeos::system::StatisticsProvider* statistics_provider, |
| const std::string& manifest) |
| : CustomizationDocument(kAcceptedManifestVersion) { |
| LoadManifestFromString(manifest); |
| Init(statistics_provider); |
| } |
| |
| StartupCustomizationDocument::~StartupCustomizationDocument() {} |
| |
| StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() { |
| return base::Singleton< |
| StartupCustomizationDocument, |
| base::DefaultSingletonTraits<StartupCustomizationDocument>>::get(); |
| } |
| |
| void StartupCustomizationDocument::Init( |
| chromeos::system::StatisticsProvider* statistics_provider) { |
| if (IsReady()) { |
| root_->GetString(kInitialLocaleAttr, &initial_locale_); |
| root_->GetString(kInitialTimezoneAttr, &initial_timezone_); |
| root_->GetString(kKeyboardLayoutAttr, &keyboard_layout_); |
| |
| std::string hwid; |
| if (statistics_provider->GetMachineStatistic( |
| chromeos::system::kHardwareClassKey, &hwid)) { |
| base::ListValue* hwid_list = NULL; |
| if (root_->GetList(kHwidMapAttr, &hwid_list)) { |
| for (const base::Value& hwid_value : hwid_list->GetList()) { |
| const base::DictionaryValue* hwid_dictionary = nullptr; |
| if (hwid_value.is_dict()) |
| hwid_dictionary = &base::Value::AsDictionaryValue(hwid_value); |
| |
| std::string hwid_mask; |
| if (hwid_dictionary && |
| hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) { |
| if (base::MatchPattern(hwid, hwid_mask)) { |
| // If HWID for this machine matches some mask, use HWID specific |
| // settings. |
| std::string result; |
| if (hwid_dictionary->GetString(kInitialLocaleAttr, &result)) |
| initial_locale_ = result; |
| |
| if (hwid_dictionary->GetString(kInitialTimezoneAttr, &result)) |
| initial_timezone_ = result; |
| |
| if (hwid_dictionary->GetString(kKeyboardLayoutAttr, &result)) |
| keyboard_layout_ = result; |
| } |
| // Don't break here to allow other entires to be applied if match. |
| } else { |
| LOG(ERROR) << "Syntax error in customization manifest"; |
| } |
| } |
| } |
| } else { |
| LOG(ERROR) << "HWID is missing in machine statistics"; |
| } |
| } |
| |
| // If manifest doesn't exist still apply values from VPD. |
| statistics_provider->GetMachineStatistic(chromeos::system::kInitialLocaleKey, |
| &initial_locale_); |
| statistics_provider->GetMachineStatistic( |
| chromeos::system::kInitialTimezoneKey, &initial_timezone_); |
| statistics_provider->GetMachineStatistic(chromeos::system::kKeyboardLayoutKey, |
| &keyboard_layout_); |
| configured_locales_ = base::SplitString( |
| initial_locale_, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| // Convert ICU locale to chrome ("en_US" to "en-US", etc.). |
| std::for_each(configured_locales_.begin(), configured_locales_.end(), |
| base::i18n::GetCanonicalLocale); |
| |
| // Let's always have configured_locales_.front() a valid entry. |
| if (configured_locales_.size() == 0) |
| configured_locales_.push_back(std::string()); |
| } |
| |
| const std::vector<std::string>& |
| StartupCustomizationDocument::configured_locales() const { |
| return configured_locales_; |
| } |
| |
| const std::string& StartupCustomizationDocument::initial_locale_default() |
| const { |
| DCHECK(configured_locales_.size() > 0); |
| return configured_locales_.front(); |
| } |
| |
| std::string StartupCustomizationDocument::GetEULAPage( |
| const std::string& locale) const { |
| return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr); |
| } |
| |
| // ServicesCustomizationDocument implementation. ------------------------------- |
| |
| class ServicesCustomizationDocument::ApplyingTask { |
| public: |
| // Registers in ServicesCustomizationDocument; |
| explicit ApplyingTask(ServicesCustomizationDocument* document); |
| |
| // Do not automatically deregister as we might be called on invalid thread. |
| ~ApplyingTask(); |
| |
| // Mark task finished and check for customization applied. |
| void Finished(bool success); |
| |
| private: |
| ServicesCustomizationDocument* document_; |
| |
| // This is error-checking flag to prevent destroying unfinished task |
| // or double finish. |
| bool engaged_; |
| }; |
| |
| ServicesCustomizationDocument::ApplyingTask::ApplyingTask( |
| ServicesCustomizationDocument* document) |
| : document_(document), engaged_(true) { |
| document->ApplyingTaskStarted(); |
| } |
| |
| ServicesCustomizationDocument::ApplyingTask::~ApplyingTask() { |
| DCHECK(!engaged_); |
| } |
| |
| void ServicesCustomizationDocument::ApplyingTask::Finished(bool success) { |
| DCHECK(engaged_); |
| if (engaged_) { |
| engaged_ = false; |
| document_->ApplyingTaskFinished(success); |
| } |
| } |
| |
| ServicesCustomizationDocument::ServicesCustomizationDocument() |
| : CustomizationDocument(kAcceptedManifestVersion), |
| num_retries_(0), |
| load_started_(false), |
| network_delay_(base::Milliseconds(kDefaultNetworkRetryDelayMS)), |
| apply_tasks_started_(0), |
| apply_tasks_finished_(0), |
| apply_tasks_success_(0) {} |
| |
| ServicesCustomizationDocument::ServicesCustomizationDocument( |
| const std::string& manifest) |
| : CustomizationDocument(kAcceptedManifestVersion), |
| network_delay_(base::Milliseconds(kDefaultNetworkRetryDelayMS)), |
| apply_tasks_started_(0), |
| apply_tasks_finished_(0), |
| apply_tasks_success_(0) { |
| LoadManifestFromString(manifest); |
| } |
| |
| ServicesCustomizationDocument::~ServicesCustomizationDocument() {} |
| |
| // static |
| ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() { |
| if (g_test_overrides) |
| return g_test_overrides->customization_document; |
| |
| return base::Singleton< |
| ServicesCustomizationDocument, |
| base::DefaultSingletonTraits<ServicesCustomizationDocument>>::get(); |
| } |
| |
| // static |
| void ServicesCustomizationDocument::RegisterPrefs( |
| PrefRegistrySimple* registry) { |
| registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false); |
| registry->RegisterStringPref(prefs::kCustomizationDefaultWallpaperURL, |
| std::string()); |
| } |
| |
| // static |
| void ServicesCustomizationDocument::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterDictionaryPref(kServicesCustomizationKey); |
| } |
| |
| // static |
| bool ServicesCustomizationDocument::WasOOBECustomizationApplied() { |
| PrefService* prefs = g_browser_process->local_state(); |
| // prefs can be NULL in some tests. |
| if (prefs) |
| return prefs->GetBoolean(kServicesCustomizationAppliedPref); |
| else |
| return false; |
| } |
| |
| // static |
| void ServicesCustomizationDocument::SetApplied(bool val) { |
| PrefService* prefs = g_browser_process->local_state(); |
| // prefs can be NULL in some tests. |
| if (prefs) |
| prefs->SetBoolean(kServicesCustomizationAppliedPref, val); |
| } |
| |
| // static |
| base::FilePath ServicesCustomizationDocument::GetCustomizedWallpaperCacheDir() { |
| base::FilePath custom_wallpaper_dir; |
| if (!base::PathService::Get(chrome::DIR_CHROMEOS_CUSTOM_WALLPAPERS, |
| &custom_wallpaper_dir)) { |
| LOG(DFATAL) << "Unable to get custom wallpaper dir."; |
| return base::FilePath(); |
| } |
| return custom_wallpaper_dir.Append(kCustomizationDefaultWallpaperDir); |
| } |
| |
| // static |
| base::FilePath |
| ServicesCustomizationDocument::GetCustomizedWallpaperDownloadedFileName() { |
| const base::FilePath dir = GetCustomizedWallpaperCacheDir(); |
| if (dir.empty()) { |
| NOTREACHED(); |
| return dir; |
| } |
| return dir.Append(kCustomizationDefaultWallpaperDownloadedFile); |
| } |
| |
| void ServicesCustomizationDocument::EnsureCustomizationApplied() { |
| if (WasOOBECustomizationApplied()) |
| return; |
| |
| // When customization manifest is fetched, applying will start automatically. |
| if (IsReady()) |
| return; |
| |
| StartFetching(); |
| } |
| |
| base::OnceClosure |
| ServicesCustomizationDocument::EnsureCustomizationAppliedClosure() { |
| return base::BindOnce( |
| &ServicesCustomizationDocument::EnsureCustomizationApplied, |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| void ServicesCustomizationDocument::StartFetching() { |
| if (IsReady() || load_started_) |
| return; |
| |
| if (!url_.is_valid()) { |
| std::string customization_id; |
| chromeos::system::StatisticsProvider* provider = |
| chromeos::system::StatisticsProvider::GetInstance(); |
| if (provider->GetMachineStatistic(chromeos::system::kCustomizationIdKey, |
| &customization_id) && |
| !customization_id.empty()) { |
| url_ = GURL(base::StringPrintf( |
| kManifestUrl, base::ToLowerASCII(customization_id).c_str())); |
| } else { |
| // Remember that there is no customization ID in VPD. |
| OnCustomizationNotFound(); |
| return; |
| } |
| } |
| |
| if (url_.is_valid()) { |
| load_started_ = true; |
| if (url_.SchemeIsFile()) { |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()}, |
| base::BindOnce(&ReadFileInBackground, base::FilePath(url_.path())), |
| base::BindOnce(&ServicesCustomizationDocument::OnManifestRead, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| StartFileFetch(); |
| } |
| } |
| } |
| |
| void ServicesCustomizationDocument::OnManifestRead( |
| const std::string& manifest) { |
| if (!manifest.empty()) |
| LoadManifestFromString(manifest); |
| |
| load_started_ = false; |
| } |
| |
| void ServicesCustomizationDocument::StartFileFetch() { |
| DelayNetworkCall( |
| network_delay_, |
| base::BindOnce(&ServicesCustomizationDocument::DoStartFileFetch, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ServicesCustomizationDocument::DoStartFileFetch() { |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = url_; |
| request->load_flags = net::LOAD_DISABLE_CACHE; |
| request->credentials_mode = network::mojom::CredentialsMode::kOmit; |
| request->headers.SetHeader("Accept", "application/json"); |
| |
| url_loader_ = network::SimpleURLLoader::Create( |
| std::move(request), kCustomizationDocumentNetworkTag); |
| |
| url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| g_test_overrides ? g_test_overrides->url_loader_factory.get() |
| : g_browser_process->shared_url_loader_factory().get(), |
| base::BindOnce(&ServicesCustomizationDocument::OnSimpleLoaderComplete, |
| base::Unretained(this))); |
| } |
| |
| bool ServicesCustomizationDocument::LoadManifestFromString( |
| const std::string& manifest) { |
| if (CustomizationDocument::LoadManifestFromString(manifest)) { |
| LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_SUCCESS); |
| OnManifestLoaded(); |
| return true; |
| } |
| |
| LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_PARSING_ERROR); |
| return false; |
| } |
| |
| void ServicesCustomizationDocument::OnManifestLoaded() { |
| if (!WasOOBECustomizationApplied()) |
| ApplyOOBECustomization(); |
| |
| std::unique_ptr<base::DictionaryValue> prefs = |
| GetDefaultAppsInProviderFormat(*root_); |
| for (ExternalLoaders::iterator it = external_loaders_.begin(); |
| it != external_loaders_.end(); ++it) { |
| if (*it) { |
| UpdateCachedManifest((*it)->profile()); |
| (*it)->SetCurrentApps( |
| std::unique_ptr<base::DictionaryValue>(prefs->DeepCopy())); |
| SetOemFolderName((*it)->profile(), *root_); |
| } |
| } |
| } |
| |
| void ServicesCustomizationDocument::OnSimpleLoaderComplete( |
| std::unique_ptr<std::string> response_body) { |
| int response_code = -1; |
| std::string mime_type; |
| if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) { |
| response_code = url_loader_->ResponseInfo()->headers->response_code(); |
| url_loader_->ResponseInfo()->headers->GetMimeType(&mime_type); |
| } |
| |
| if (response_body && mime_type == "application/json") { |
| LoadManifestFromString(*response_body); |
| } else if (response_code == net::HTTP_NOT_FOUND) { |
| LOG(ERROR) << "Customization manifest is missing on server: " |
| << url_.spec(); |
| OnCustomizationNotFound(); |
| } else { |
| if (num_retries_ < kMaxFetchRetries) { |
| num_retries_++; |
| content::GetUIThreadTaskRunner({})->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ServicesCustomizationDocument::StartFileFetch, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Seconds(kRetriesDelayInSec)); |
| return; |
| } |
| // This doesn't stop fetching manifest on next restart. |
| LOG(ERROR) << "URL fetch for services customization failed:" |
| << " response code = " << response_code |
| << " URL = " << url_.spec(); |
| |
| LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_RETRIES_FAIL); |
| } |
| load_started_ = false; |
| } |
| |
| bool ServicesCustomizationDocument::ApplyOOBECustomization() { |
| if (apply_tasks_started_) |
| return false; |
| |
| CheckAndApplyWallpaper(); |
| return false; |
| } |
| |
| bool ServicesCustomizationDocument::GetDefaultWallpaperUrl( |
| GURL* out_url) const { |
| if (!IsReady()) |
| return false; |
| |
| std::string url; |
| if (!root_->GetString(kDefaultWallpaperAttr, &url)) |
| return false; |
| |
| *out_url = GURL(url); |
| return true; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> |
| ServicesCustomizationDocument::GetDefaultApps() const { |
| if (!IsReady()) |
| return nullptr; |
| |
| return GetDefaultAppsInProviderFormat(*root_); |
| } |
| |
| std::string ServicesCustomizationDocument::GetOemAppsFolderName( |
| const std::string& locale) const { |
| if (!IsReady()) |
| return std::string(); |
| |
| return GetOemAppsFolderNameImpl(locale, *root_); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> |
| ServicesCustomizationDocument::GetDefaultAppsInProviderFormat( |
| const base::DictionaryValue& root) { |
| std::unique_ptr<base::DictionaryValue> prefs(new base::DictionaryValue); |
| const base::ListValue* apps_list = NULL; |
| if (root.GetList(kDefaultAppsAttr, &apps_list)) { |
| for (size_t i = 0; i < apps_list->GetList().size(); ++i) { |
| std::string app_id; |
| std::unique_ptr<base::DictionaryValue> entry; |
| const base::Value& app_entry_value = apps_list->GetList()[i]; |
| if (app_entry_value.is_string()) { |
| app_id = app_entry_value.GetString(); |
| entry = std::make_unique<base::DictionaryValue>(); |
| } else if (app_entry_value.is_dict()) { |
| const base::DictionaryValue& app_entry = |
| base::Value::AsDictionaryValue(app_entry_value); |
| if (!app_entry.GetString(kIdAttr, &app_id)) { |
| LOG(ERROR) << "Wrong format of default application list"; |
| prefs->DictClear(); |
| break; |
| } |
| entry = app_entry.CreateDeepCopy(); |
| entry->RemoveKey(kIdAttr); |
| } else { |
| LOG(ERROR) << "Wrong format of default application list"; |
| prefs->DictClear(); |
| break; |
| } |
| if (!entry->FindKey( |
| extensions::ExternalProviderImpl::kExternalUpdateUrl)) { |
| entry->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl, |
| extension_urls::GetWebstoreUpdateUrl().spec()); |
| } |
| prefs->SetPath(app_id, base::Value::FromUniquePtrValue(std::move(entry))); |
| } |
| } |
| |
| return prefs; |
| } |
| |
| void ServicesCustomizationDocument::UpdateCachedManifest(Profile* profile) { |
| profile->GetPrefs()->Set(kServicesCustomizationKey, *root_); |
| } |
| |
| extensions::ExternalLoader* ServicesCustomizationDocument::CreateExternalLoader( |
| Profile* profile) { |
| ServicesCustomizationExternalLoader* loader = |
| new ServicesCustomizationExternalLoader(profile); |
| external_loaders_.push_back(loader->AsWeakPtr()); |
| |
| if (IsReady()) { |
| UpdateCachedManifest(profile); |
| loader->SetCurrentApps(GetDefaultAppsInProviderFormat(*root_)); |
| SetOemFolderName(profile, *root_); |
| } else { |
| const base::DictionaryValue* root = &base::Value::AsDictionaryValue( |
| *profile->GetPrefs()->GetDictionary(kServicesCustomizationKey)); |
| if (root && root->FindStringKey(kVersionAttr)) { |
| // If version exists, profile has cached version of customization. |
| loader->SetCurrentApps(GetDefaultAppsInProviderFormat(*root)); |
| SetOemFolderName(profile, *root); |
| } else { |
| // StartFetching will be called from ServicesCustomizationExternalLoader |
| // when StartLoading is called. We can't initiate manifest fetch here |
| // because caller may never call StartLoading for the provider. |
| } |
| } |
| |
| return loader; |
| } |
| |
| void ServicesCustomizationDocument::OnCustomizationNotFound() { |
| LogManifestLoadResult(HISTOGRAM_LOAD_RESULT_FILE_NOT_FOUND); |
| LoadManifestFromString(kEmptyServicesCustomizationManifest); |
| } |
| |
| void ServicesCustomizationDocument::SetOemFolderName( |
| Profile* profile, |
| const base::DictionaryValue& root) { |
| std::string locale = g_browser_process->GetApplicationLocale(); |
| std::string name = GetOemAppsFolderNameImpl(locale, root); |
| if (name.empty()) |
| name = chromeos::default_app_order::GetOemAppsFolderName(); |
| if (!name.empty()) { |
| app_list::AppListSyncableService* service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(profile); |
| if (!service) { |
| LOG(WARNING) << "AppListSyncableService is not ready for setting OEM " |
| "folder name"; |
| return; |
| } |
| service->SetOemFolderName(name); |
| } |
| } |
| |
| std::string ServicesCustomizationDocument::GetOemAppsFolderNameImpl( |
| const std::string& locale, |
| const base::DictionaryValue& root) const { |
| return GetLocaleSpecificStringImpl( |
| &root, locale, kLocalizedContent, kDefaultAppsFolderName); |
| } |
| |
| // static |
| void ServicesCustomizationDocument::InitializeForTesting( |
| scoped_refptr<network::SharedURLLoaderFactory> factory) { |
| g_test_overrides = new CustomizationDocumentTestOverride; |
| g_test_overrides->customization_document = new ServicesCustomizationDocument; |
| g_test_overrides->customization_document->network_delay_ = base::TimeDelta(); |
| g_test_overrides->url_loader_factory = std::move(factory); |
| } |
| |
| // static |
| void ServicesCustomizationDocument::ShutdownForTesting() { |
| delete g_test_overrides->customization_document; |
| delete g_test_overrides; |
| g_test_overrides = nullptr; |
| } |
| |
| void ServicesCustomizationDocument::StartOEMWallpaperDownload( |
| const GURL& wallpaper_url, |
| std::unique_ptr<ServicesCustomizationDocument::ApplyingTask> applying) { |
| DCHECK(wallpaper_url.is_valid()); |
| |
| const base::FilePath dir = GetCustomizedWallpaperCacheDir(); |
| const base::FilePath file = GetCustomizedWallpaperDownloadedFileName(); |
| if (dir.empty() || file.empty()) { |
| NOTREACHED(); |
| applying->Finished(false); |
| return; |
| } |
| |
| wallpaper_downloader_ = std::make_unique<CustomizationWallpaperDownloader>( |
| wallpaper_url, dir, file, |
| base::BindOnce(&ServicesCustomizationDocument::OnOEMWallpaperDownloaded, |
| weak_ptr_factory_.GetWeakPtr(), std::move(applying))); |
| |
| wallpaper_downloader_->Start(); |
| } |
| |
| void ServicesCustomizationDocument::CheckAndApplyWallpaper() { |
| if (wallpaper_downloader_.get()) { |
| VLOG(1) << "CheckAndApplyWallpaper(): download has already started."; |
| return; |
| } |
| std::unique_ptr<ServicesCustomizationDocument::ApplyingTask> applying( |
| new ServicesCustomizationDocument::ApplyingTask(this)); |
| |
| GURL wallpaper_url; |
| if (!GetDefaultWallpaperUrl(&wallpaper_url)) { |
| PrefService* pref_service = g_browser_process->local_state(); |
| std::string current_url = |
| pref_service->GetString(prefs::kCustomizationDefaultWallpaperURL); |
| if (!current_url.empty()) { |
| VLOG(1) << "ServicesCustomizationDocument::CheckAndApplyWallpaper() : " |
| << "No wallpaper URL attribute in customization document, " |
| << "but current value is non-empty: '" << current_url |
| << "'. Ignored."; |
| } |
| applying->Finished(true); |
| return; |
| } |
| |
| // Should fail if this ever happens in tests. |
| DCHECK(wallpaper_url.is_valid()); |
| if (!wallpaper_url.is_valid()) { |
| if (!wallpaper_url.is_empty()) { |
| LOG(WARNING) << "Invalid Customized Wallpaper URL '" |
| << wallpaper_url.spec() << "'."; |
| } |
| applying->Finished(false); |
| return; |
| } |
| |
| std::unique_ptr<bool> exists(new bool(false)); |
| |
| base::OnceClosure check_file_exists = base::BindOnce( |
| &CheckWallpaperCacheExists, GetCustomizedWallpaperDownloadedFileName(), |
| base::Unretained(exists.get())); |
| base::OnceClosure on_checked_closure = base::BindOnce( |
| &ServicesCustomizationDocument::OnCheckedWallpaperCacheExists, |
| weak_ptr_factory_.GetWeakPtr(), std::move(exists), std::move(applying)); |
| base::ThreadPool::PostTaskAndReply( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, |
| std::move(check_file_exists), std::move(on_checked_closure)); |
| } |
| |
| void ServicesCustomizationDocument::OnCheckedWallpaperCacheExists( |
| std::unique_ptr<bool> exists, |
| std::unique_ptr<ServicesCustomizationDocument::ApplyingTask> applying) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(exists); |
| DCHECK(applying); |
| |
| ApplyWallpaper(*exists, std::move(applying)); |
| } |
| |
| void ServicesCustomizationDocument::ApplyWallpaper( |
| bool default_wallpaper_file_exists, |
| std::unique_ptr<ServicesCustomizationDocument::ApplyingTask> applying) { |
| GURL wallpaper_url; |
| const bool wallpaper_url_present = GetDefaultWallpaperUrl(&wallpaper_url); |
| |
| PrefService* pref_service = g_browser_process->local_state(); |
| |
| std::string current_url = |
| pref_service->GetString(prefs::kCustomizationDefaultWallpaperURL); |
| if (current_url != wallpaper_url.spec()) { |
| if (wallpaper_url_present) { |
| VLOG(1) << "ServicesCustomizationDocument::ApplyWallpaper() : " |
| << "Wallpaper URL in customization document '" |
| << wallpaper_url.spec() << "' differs from current '" |
| << current_url << "'." |
| << (GURL(current_url).is_valid() && default_wallpaper_file_exists |
| ? " Ignored." |
| : " Will refetch."); |
| } else { |
| VLOG(1) << "ServicesCustomizationDocument::ApplyWallpaper() : " |
| << "No wallpaper URL attribute in customization document, " |
| << "but current value is non-empty: '" << current_url |
| << "'. Ignored."; |
| } |
| } |
| if (!wallpaper_url_present) { |
| applying->Finished(true); |
| return; |
| } |
| |
| DCHECK(wallpaper_url.is_valid()); |
| |
| // Never update system-wide wallpaper (i.e. do not check |
| // current_url == wallpaper_url.spec() ) |
| if (GURL(current_url).is_valid() && default_wallpaper_file_exists) { |
| VLOG(1) |
| << "ServicesCustomizationDocument::ApplyWallpaper() : reuse existing"; |
| OnOEMWallpaperDownloaded(std::move(applying), true, GURL(current_url)); |
| } else { |
| VLOG(1) |
| << "ServicesCustomizationDocument::ApplyWallpaper() : start download"; |
| StartOEMWallpaperDownload(wallpaper_url, std::move(applying)); |
| } |
| } |
| |
| void ServicesCustomizationDocument::OnOEMWallpaperDownloaded( |
| std::unique_ptr<ServicesCustomizationDocument::ApplyingTask> applying, |
| bool success, |
| const GURL& wallpaper_url) { |
| if (success) { |
| DCHECK(wallpaper_url.is_valid()); |
| |
| VLOG(1) << "Setting default wallpaper to '" |
| << GetCustomizedWallpaperDownloadedFileName().value() << "' ('" |
| << wallpaper_url.spec() << "')"; |
| customization_wallpaper_util::StartSettingCustomizedDefaultWallpaper( |
| wallpaper_url, GetCustomizedWallpaperDownloadedFileName()); |
| } |
| wallpaper_downloader_.reset(); |
| applying->Finished(success); |
| } |
| |
| void ServicesCustomizationDocument::ApplyingTaskStarted() { |
| ++apply_tasks_started_; |
| } |
| |
| void ServicesCustomizationDocument::ApplyingTaskFinished(bool success) { |
| DCHECK_GT(apply_tasks_started_, apply_tasks_finished_); |
| ++apply_tasks_finished_; |
| |
| apply_tasks_success_ += success; |
| |
| if (apply_tasks_started_ != apply_tasks_finished_) |
| return; |
| |
| if (apply_tasks_success_ == apply_tasks_finished_) |
| SetApplied(true); |
| } |
| |
| } // namespace ash |