// 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/chromeos/customization_document.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/system/statistics_provider.h"
#include "content/public/browser/browser_thread.h"
#include "net/url_request/url_fetcher.h"
using content::BrowserThread;
// Manifest attributes names.
namespace {
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 kRegistrationUrlAttr[] = "registration_url";
const char kHwidMapAttr[] = "hwid_map";
const char kHwidMaskAttr[] = "hwid_mask";
const char kSetupContentAttr[] = "setup_content";
const char kHelpPageAttr[] = "help_page";
const char kEulaPageAttr[] = "eula_page";
const char kAppContentAttr[] = "app_content";
const char kInitialStartPageAttr[] = "initial_start_page";
const char kSupportPageAttr[] = "support_page";
const char kAcceptedManifestVersion[] = "1.0";
// Path to OEM partner startup customization manifest.
const char kStartupCustomizationManifestPath[] =
"/opt/oem/etc/startup_manifest.json";
// URL where to fetch OEM services customization manifest from.
const char kServicesCustomizationManifestUrl[] =
"file:///opt/oem/etc/services_manifest.json";
// 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;
} // anonymous namespace
namespace chromeos {
// 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) {
int error_code = 0;
std::string error;
scoped_ptr<Value> root(base::JSONReader::ReadAndReturnError(manifest,
base::JSON_ALLOW_TRAILING_COMMAS, &error_code, &error));
if (error_code != base::JSONReader::JSON_NO_ERROR)
LOG(ERROR) << error;
DCHECK(root.get() != NULL);
if (root.get() == NULL)
return false;
DCHECK(root->GetType() == Value::TYPE_DICTIONARY);
if (root->GetType() == Value::TYPE_DICTIONARY) {
root_.reset(static_cast<DictionaryValue*>(root.release()));
std::string result;
if (root_->GetString(kVersionAttr, &result) &&
result == accepted_version_)
return true;
LOG(ERROR) << "Wrong customization manifest version";
root_.reset(NULL);
}
return false;
}
std::string CustomizationDocument::GetLocaleSpecificString(
const std::string& locale,
const std::string& dictionary_name,
const std::string& entry_name) const {
DictionaryValue* dictionary_content = NULL;
if (!root_.get() ||
!root_->GetDictionary(dictionary_name, &dictionary_content))
return std::string();
DictionaryValue* locale_dictionary = NULL;
if (dictionary_content->GetDictionary(locale, &locale_dictionary)) {
std::string result;
if (locale_dictionary->GetString(entry_name, &result))
return result;
}
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();
}
// 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;
LoadManifestFromFile(base::FilePath(kStartupCustomizationManifestPath));
}
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 Singleton<StartupCustomizationDocument,
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_);
root_->GetString(kRegistrationUrlAttr, ®istration_url_);
std::string hwid;
if (statistics_provider->GetMachineStatistic(
chromeos::system::kHardwareClassKey, &hwid)) {
ListValue* hwid_list = NULL;
if (root_->GetList(kHwidMapAttr, &hwid_list)) {
for (size_t i = 0; i < hwid_list->GetSize(); ++i) {
DictionaryValue* hwid_dictionary = NULL;
std::string hwid_mask;
if (hwid_list->GetDictionary(i, &hwid_dictionary) &&
hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) {
if (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(kInitialLocaleAttr,
&initial_locale_);
statistics_provider->GetMachineStatistic(kInitialTimezoneAttr,
&initial_timezone_);
statistics_provider->GetMachineStatistic(kKeyboardLayoutAttr,
&keyboard_layout_);
}
std::string StartupCustomizationDocument::GetHelpPage(
const std::string& locale) const {
return GetLocaleSpecificString(locale, kSetupContentAttr, kHelpPageAttr);
}
std::string StartupCustomizationDocument::GetEULAPage(
const std::string& locale) const {
return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
}
// ServicesCustomizationDocument implementation. -------------------------------
ServicesCustomizationDocument::ServicesCustomizationDocument()
: CustomizationDocument(kAcceptedManifestVersion),
url_(kServicesCustomizationManifestUrl) {
}
ServicesCustomizationDocument::ServicesCustomizationDocument(
const std::string& manifest)
: CustomizationDocument(kAcceptedManifestVersion) {
LoadManifestFromString(manifest);
}
ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
// static
ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() {
return Singleton<ServicesCustomizationDocument,
DefaultSingletonTraits<ServicesCustomizationDocument> >::get();
}
// static
void ServicesCustomizationDocument::RegisterPrefs(
PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
}
// static
bool ServicesCustomizationDocument::WasApplied() {
PrefService* prefs = g_browser_process->local_state();
return prefs->GetBoolean(kServicesCustomizationAppliedPref);
}
// static
void ServicesCustomizationDocument::SetApplied(bool val) {
PrefService* prefs = g_browser_process->local_state();
prefs->SetBoolean(kServicesCustomizationAppliedPref, val);
}
void ServicesCustomizationDocument::StartFetching() {
if (url_.SchemeIsFile()) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&ServicesCustomizationDocument::ReadFileInBackground,
base::Unretained(this), // this class is a singleton.
base::FilePath(url_.path())));
} else {
StartFileFetch();
}
}
void ServicesCustomizationDocument::ReadFileInBackground(
const base::FilePath& file) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
std::string manifest;
if (base::ReadFileToString(file, &manifest)) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(
base::IgnoreResult(
&ServicesCustomizationDocument::LoadManifestFromString),
base::Unretained(this), // this class is a singleton.
manifest));
} else {
VLOG(1) << "Failed to load services customization manifest from: "
<< file.value();
}
}
void ServicesCustomizationDocument::StartFileFetch() {
DCHECK(url_.is_valid());
url_fetcher_.reset(net::URLFetcher::Create(
url_, net::URLFetcher::GET, this));
url_fetcher_->SetRequestContext(
ProfileManager::GetDefaultProfile()->GetRequestContext());
url_fetcher_->Start();
}
void ServicesCustomizationDocument::OnURLFetchComplete(
const net::URLFetcher* source) {
if (source->GetResponseCode() == 200) {
std::string data;
source->GetResponseAsString(&data);
LoadManifestFromString(data);
} else {
const NetworkState* default_network =
NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
if (default_network && default_network->IsConnectedState() &&
num_retries_ < kMaxFetchRetries) {
num_retries_++;
retry_timer_.Start(FROM_HERE,
base::TimeDelta::FromSeconds(kRetriesDelayInSec),
this, &ServicesCustomizationDocument::StartFileFetch);
return;
}
LOG(ERROR) << "URL fetch for services customization failed:"
<< " response code = " << source->GetResponseCode()
<< " URL = " << source->GetURL().spec();
}
}
bool ServicesCustomizationDocument::ApplyCustomization() {
// TODO(dpolukhin): apply customized apps, exts and support page.
SetApplied(true);
return true;
}
std::string ServicesCustomizationDocument::GetInitialStartPage(
const std::string& locale) const {
return GetLocaleSpecificString(
locale, kAppContentAttr, kInitialStartPageAttr);
}
std::string ServicesCustomizationDocument::GetSupportPage(
const std::string& locale) const {
return GetLocaleSpecificString(
locale, kAppContentAttr, kSupportPageAttr);
}
} // namespace chromeos