// 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/mobile_config.h"
#include <algorithm>
#include "base/bind.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/startup_utils.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace {
// Config attributes names.
const char kAcceptedConfigVersion[] = "1.0";
const char kDefaultAttr[] = "default";
// Carrier config attributes.
const char kCarriersAttr[] = "carriers";
const char kCarrierIdsAttr[] = "ids";
const char kCarrierIdAttr[] = "id";
const char kTopUpURLAttr[] = "top_up_url";
const char kShowPortalButtonAttr[] = "show_portal_button";
const char kDealsAttr[] = "deals";
// Carrier deal attributes.
const char kDealIdAttr[] = "deal_id";
const char kDealLocalesAttr[] = "locales";
const char kInfoURLAttr[] = "info_url";
const char kNotificationCountAttr[] = "notification_count";
const char kDealExpireDateAttr[] = "expire_date";
const char kLocalizedContentAttr[] = "localized_content";
// Initial locale carrier config attributes.
const char kInitialLocalesAttr[] = "initial_locales";
const char kSetupURLAttr[] = "setup_url";
// Local config properties.
const char kExcludeDealsAttr[] = "exclude_deals";
// Location of the global carrier config.
const char kGlobalCarrierConfigPath[] =
"/usr/share/chromeos-assets/mobile/carrier_config.json";
// Location of the local carrier config.
const char kLocalCarrierConfigPath[] =
"/opt/oem/etc/carrier_config.json";
} // anonymous namespace
namespace chromeos {
// MobileConfig::CarrierDeal implementation. -----------------------------------
MobileConfig::CarrierDeal::CarrierDeal(const DictionaryValue* deal_dict)
: notification_count_(0),
localized_strings_(NULL) {
deal_dict->GetString(kDealIdAttr, &deal_id_);
// Extract list of deal locales.
const ListValue* locale_list = NULL;
if (deal_dict->GetList(kDealLocalesAttr, &locale_list)) {
for (size_t i = 0; i < locale_list->GetSize(); ++i) {
std::string locale;
if (locale_list->GetString(i, &locale))
locales_.push_back(locale);
}
}
deal_dict->GetString(kInfoURLAttr, &info_url_);
deal_dict->GetInteger(kNotificationCountAttr, ¬ification_count_);
std::string date_string;
if (deal_dict->GetString(kDealExpireDateAttr, &date_string)) {
if (!base::Time::FromString(date_string.c_str(), &expire_date_))
LOG(ERROR) << "Error parsing deal_expire_date: " << date_string;
}
deal_dict->GetDictionary(kLocalizedContentAttr, &localized_strings_);
}
MobileConfig::CarrierDeal::~CarrierDeal() {
}
std::string MobileConfig::CarrierDeal::GetLocalizedString(
const std::string& locale, const std::string& id) const {
std::string result;
if (localized_strings_) {
const DictionaryValue* locale_dict = NULL;
if (localized_strings_->GetDictionary(locale, &locale_dict) &&
locale_dict->GetString(id, &result)) {
return result;
} else if (localized_strings_->GetDictionary(kDefaultAttr, &locale_dict) &&
locale_dict->GetString(id, &result)) {
return result;
}
}
return result;
}
// MobileConfig::Carrier implementation. ---------------------------------------
MobileConfig::Carrier::Carrier(const DictionaryValue* carrier_dict,
const std::string& initial_locale)
: show_portal_button_(false) {
InitFromDictionary(carrier_dict, initial_locale);
}
MobileConfig::Carrier::~Carrier() {
RemoveDeals();
}
const MobileConfig::CarrierDeal* MobileConfig::Carrier::GetDefaultDeal() const {
// TODO(nkostylev): Use carrier "default_deal_id" attribute.
CarrierDeals::const_iterator iter = deals_.begin();
if (iter != deals_.end())
return GetDeal((*iter).first);
else
return NULL;
}
const MobileConfig::CarrierDeal* MobileConfig::Carrier::GetDeal(
const std::string& deal_id) const {
CarrierDeals::const_iterator iter = deals_.find(deal_id);
if (iter != deals_.end()) {
CarrierDeal* deal = iter->second;
// Make sure that deal is still active,
// i.e. if deal expire date is defined, check it.
if (!deal->expire_date().is_null() &&
deal->expire_date() <= base::Time::Now()) {
return NULL;
}
return deal;
} else {
return NULL;
}
}
void MobileConfig::Carrier::InitFromDictionary(
const base::DictionaryValue* carrier_dict,
const std::string& initial_locale) {
carrier_dict->GetString(kTopUpURLAttr, &top_up_url_);
carrier_dict->GetBoolean(kShowPortalButtonAttr, &show_portal_button_);
bool exclude_deals = false;
if (carrier_dict->GetBoolean(kExcludeDealsAttr, &exclude_deals) &&
exclude_deals) {
RemoveDeals();
}
// Extract list of external IDs for this carrier.
const ListValue* id_list = NULL;
if (carrier_dict->GetList(kCarrierIdsAttr, &id_list)) {
for (size_t i = 0; i < id_list->GetSize(); ++i) {
const DictionaryValue* id_dict = NULL;
std::string external_id;
if (id_list->GetDictionary(i, &id_dict) &&
id_dict->GetString(kCarrierIdAttr, &external_id)) {
external_ids_.push_back(external_id);
}
}
}
// Extract list of deals for this carrier.
const ListValue* deals_list = NULL;
if (carrier_dict->GetList(kDealsAttr, &deals_list)) {
for (size_t i = 0; i < deals_list->GetSize(); ++i) {
const DictionaryValue* deal_dict = NULL;
if (deals_list->GetDictionary(i, &deal_dict)) {
scoped_ptr<CarrierDeal> deal(new CarrierDeal(deal_dict));
// Filter out deals by initial_locale right away.
std::vector<std::string>::const_iterator iter =
std::find(deal->locales().begin(),
deal->locales().end(),
initial_locale);
if (iter != deal->locales().end()) {
const std::string& deal_id = deal->deal_id();
deals_[deal_id] = deal.release();
}
}
}
}
}
void MobileConfig::Carrier::RemoveDeals() {
STLDeleteValues(&deals_);
}
// MobileConfig::LocaleConfig implementation. ----------------------------------
MobileConfig::LocaleConfig::LocaleConfig(DictionaryValue* locale_dict) {
InitFromDictionary(locale_dict);
}
MobileConfig::LocaleConfig::~LocaleConfig() {
}
void MobileConfig::LocaleConfig::InitFromDictionary(
base::DictionaryValue* locale_dict) {
locale_dict->GetString(kSetupURLAttr, &setup_url_);
}
// MobileConfig implementation, public -----------------------------------------
// static
MobileConfig* MobileConfig::GetInstance() {
return Singleton<MobileConfig,
DefaultSingletonTraits<MobileConfig> >::get();
}
const MobileConfig::Carrier* MobileConfig::GetCarrier(
const std::string& carrier_id) const {
CarrierIdMap::const_iterator id_iter = carrier_id_map_.find(carrier_id);
std::string internal_id;
if (id_iter != carrier_id_map_.end())
internal_id = id_iter->second;
else
return NULL;
Carriers::const_iterator iter = carriers_.find(internal_id);
if (iter != carriers_.end())
return iter->second;
else
return NULL;
}
const MobileConfig::LocaleConfig* MobileConfig::GetLocaleConfig() const {
return locale_config_.get();
}
// MobileConfig implementation, protected --------------------------------------
bool MobileConfig::LoadManifestFromString(const std::string& manifest) {
if (!CustomizationDocument::LoadManifestFromString(manifest))
return false;
// Local config specific attribute.
bool exclude_deals = false;
if (root_.get() &&
root_->GetBoolean(kExcludeDealsAttr, &exclude_deals) &&
exclude_deals) {
for (Carriers::iterator iter = carriers_.begin();
iter != carriers_.end(); ++iter) {
iter->second->RemoveDeals();
}
}
// Other parts are optional and are the same among global/local config.
DictionaryValue* carriers = NULL;
if (root_.get() && root_->GetDictionary(kCarriersAttr, &carriers)) {
for (DictionaryValue::Iterator iter(*carriers); !iter.IsAtEnd();
iter.Advance()) {
const DictionaryValue* carrier_dict = NULL;
if (iter.value().GetAsDictionary(&carrier_dict)) {
const std::string& internal_id = iter.key();
Carriers::iterator inner_iter = carriers_.find(internal_id);
if (inner_iter != carriers_.end()) {
// Carrier already defined i.e. loading from the local config.
// New ID mappings in local config is not supported.
inner_iter->second->InitFromDictionary(carrier_dict, initial_locale_);
} else {
Carrier* carrier = new Carrier(carrier_dict, initial_locale_);
if (!carrier->external_ids().empty()) {
// Map all external IDs to a single internal one.
for (std::vector<std::string>::const_iterator
i = carrier->external_ids().begin();
i != carrier->external_ids().end(); ++i) {
carrier_id_map_[*i] = internal_id;
}
} else {
// Trivial case - using same ID for external/internal one.
carrier_id_map_[internal_id] = internal_id;
}
carriers_[internal_id] = carrier;
}
}
}
}
DictionaryValue* initial_locales = NULL;
if (root_.get() && root_->GetDictionary(kInitialLocalesAttr,
&initial_locales)) {
DictionaryValue* locale_config_dict = NULL;
// Search for a config based on current initial locale.
if (initial_locales->GetDictionary(initial_locale_,
&locale_config_dict)) {
locale_config_.reset(new LocaleConfig(locale_config_dict));
}
}
return true;
}
// MobileConfig implementation, private ----------------------------------------
MobileConfig::MobileConfig()
: CustomizationDocument(kAcceptedConfigVersion),
initial_locale_(StartupUtils::GetInitialLocale()) {
LoadConfig();
}
MobileConfig::MobileConfig(const std::string& config,
const std::string& initial_locale)
: CustomizationDocument(kAcceptedConfigVersion),
initial_locale_(initial_locale) {
LoadManifestFromString(config);
}
MobileConfig::~MobileConfig() {
STLDeleteValues(&carriers_);
}
void MobileConfig::LoadConfig() {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&MobileConfig::ReadConfigInBackground,
base::Unretained(this), // this class is a singleton.
base::FilePath(kGlobalCarrierConfigPath),
base::FilePath(kLocalCarrierConfigPath)));
}
void MobileConfig::ProcessConfig(const std::string& global_config,
const std::string& local_config) {
// Global config is mandatory, local config is optional.
bool global_initialized = false;
bool local_initialized = true;
scoped_ptr<base::DictionaryValue> global_config_root;
if (!global_config.empty()) {
global_initialized = LoadManifestFromString(global_config);
// Backup global config root as it might be
// owerwritten while loading local config.
global_config_root.reset(root_.release());
}
if (!local_config.empty())
local_initialized = LoadManifestFromString(local_config);
// Treat any parser errors as fatal.
if (!global_initialized || !local_initialized) {
root_.reset(NULL);
local_config_root_.reset(NULL);
} else {
local_config_root_.reset(root_.release());
root_.reset(global_config_root.release());
}
}
void MobileConfig::ReadConfigInBackground(
const base::FilePath& global_config_file,
const base::FilePath& local_config_file) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
std::string global_config;
std::string local_config;
if (!base::ReadFileToString(global_config_file, &global_config)) {
VLOG(1) << "Failed to load global mobile config from: "
<< global_config_file.value();
}
if (!base::ReadFileToString(local_config_file, &local_config)) {
VLOG(1) << "Failed to load local mobile config from: "
<< local_config_file.value();
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&MobileConfig::ProcessConfig,
base::Unretained(this), // singleton.
global_config,
local_config));
}
} // namespace chromeos