1 // Copyright (c) 2011 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 "base/file_path.h"
8 #include "base/file_util.h"
9 #include "base/json/json_reader.h"
10 #include "base/logging.h"
11 #include "base/string_tokenizer.h"
12 #include "base/string_util.h"
13 #include "base/time.h"
14 #include "base/utf_string_conversions.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chromeos/cros/cros_library.h"
17 #include "chrome/browser/chromeos/cros/network_library.h"
18 #include "chrome/browser/chromeos/login/wizard_controller.h"
19 #include "chrome/browser/chromeos/system_access.h"
20 #include "chrome/browser/prefs/pref_service.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "content/browser/browser_thread.h"
23
24 // Manifest attributes names.
25
26 namespace {
27
28 const char kVersionAttr[] = "version";
29 const char kDefaultAttr[] = "default";
30 const char kInitialLocaleAttr[] = "initial_locale";
31 const char kInitialTimezoneAttr[] = "initial_timezone";
32 const char kKeyboardLayoutAttr[] = "keyboard_layout";
33 const char kRegistrationUrlAttr[] = "registration_url";
34 const char kHwidMapAttr[] = "hwid_map";
35 const char kHwidMaskAttr[] = "hwid_mask";
36 const char kSetupContentAttr[] = "setup_content";
37 const char kHelpPageAttr[] = "help_page";
38 const char kEulaPageAttr[] = "eula_page";
39 const char kAppContentAttr[] = "app_content";
40 const char kInitialStartPageAttr[] = "initial_start_page";
41 const char kSupportPageAttr[] = "support_page";
42
43 const char kAcceptedManifestVersion[] = "1.0";
44
45 const char kHwid[] = "hwid";
46
47 // Carrier deals attributes.
48 const char kCarrierDealsAttr[] = "carrier_deals";
49 const char kDealLocaleAttr[] = "deal_locale";
50 const char kTopUpURLAttr[] = "top_up_url";
51 const char kNotificationCountAttr[] = "notification_count";
52 const char kDealExpireDateAttr[] = "expire_date";
53 const char kLocalizedContentAttr[] = "localized_content";
54 const char kNotificationTextAttr[] = "notification_text";
55
56 // Path to OEM partner startup customization manifest.
57 const char kStartupCustomizationManifestPath[] =
58 "/opt/oem/etc/startup_manifest.json";
59
60 // URL where to fetch OEM services customization manifest from.
61 const char kServicesCustomizationManifestUrl[] =
62 "file:///opt/oem/etc/services_manifest.json";
63
64 // Name of local state option that tracks if services customization has been
65 // applied.
66 const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied";
67
68 // Maximum number of retries to fetch file if network is not available.
69 const int kMaxFetchRetries = 3;
70
71 // Delay between file fetch retries if network is not available.
72 const int kRetriesDelayInSec = 2;
73
74 } // anonymous namespace
75
76 DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::ServicesCustomizationDocument);
77
78 namespace chromeos {
79
80 // CustomizationDocument implementation. ---------------------------------------
81
LoadManifestFromFile(const FilePath & manifest_path)82 bool CustomizationDocument::LoadManifestFromFile(
83 const FilePath& manifest_path) {
84 std::string manifest;
85 if (!file_util::ReadFileToString(manifest_path, &manifest))
86 return false;
87 return LoadManifestFromString(manifest);
88 }
89
LoadManifestFromString(const std::string & manifest)90 bool CustomizationDocument::LoadManifestFromString(
91 const std::string& manifest) {
92 scoped_ptr<Value> root(base::JSONReader::Read(manifest, true));
93 DCHECK(root.get() != NULL);
94 if (root.get() == NULL)
95 return false;
96 DCHECK(root->GetType() == Value::TYPE_DICTIONARY);
97 if (root->GetType() == Value::TYPE_DICTIONARY) {
98 root_.reset(static_cast<DictionaryValue*>(root.release()));
99 std::string result;
100 if (root_->GetString(kVersionAttr, &result) &&
101 result == kAcceptedManifestVersion)
102 return true;
103
104 LOG(ERROR) << "Wrong customization manifest version";
105 root_.reset(NULL);
106 }
107 return false;
108 }
109
GetLocaleSpecificString(const std::string & locale,const std::string & dictionary_name,const std::string & entry_name) const110 std::string CustomizationDocument::GetLocaleSpecificString(
111 const std::string& locale,
112 const std::string& dictionary_name,
113 const std::string& entry_name) const {
114 DictionaryValue* dictionary_content = NULL;
115 if (!root_.get() ||
116 !root_->GetDictionary(dictionary_name, &dictionary_content))
117 return std::string();
118
119 DictionaryValue* locale_dictionary = NULL;
120 if (dictionary_content->GetDictionary(locale, &locale_dictionary)) {
121 std::string result;
122 if (locale_dictionary->GetString(entry_name, &result))
123 return result;
124 }
125
126 DictionaryValue* default_dictionary = NULL;
127 if (dictionary_content->GetDictionary(kDefaultAttr, &default_dictionary)) {
128 std::string result;
129 if (default_dictionary->GetString(entry_name, &result))
130 return result;
131 }
132
133 return std::string();
134 }
135
136 // StartupCustomizationDocument implementation. --------------------------------
137
StartupCustomizationDocument()138 StartupCustomizationDocument::StartupCustomizationDocument() {
139 {
140 // Loading manifest causes us to do blocking IO on UI thread.
141 // Temporarily allow it until we fix http://crosbug.com/11103
142 base::ThreadRestrictions::ScopedAllowIO allow_io;
143 LoadManifestFromFile(FilePath(kStartupCustomizationManifestPath));
144 }
145 Init(SystemAccess::GetInstance());
146 }
147
StartupCustomizationDocument(SystemAccess * system_access,const std::string & manifest)148 StartupCustomizationDocument::StartupCustomizationDocument(
149 SystemAccess* system_access, const std::string& manifest) {
150 LoadManifestFromString(manifest);
151 Init(system_access);
152 }
153
GetInstance()154 StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() {
155 return Singleton<StartupCustomizationDocument,
156 DefaultSingletonTraits<StartupCustomizationDocument> >::get();
157 }
158
Init(SystemAccess * system_access)159 void StartupCustomizationDocument::Init(SystemAccess* system_access) {
160 if (!IsReady())
161 return;
162
163 root_->GetString(kInitialLocaleAttr, &initial_locale_);
164 root_->GetString(kInitialTimezoneAttr, &initial_timezone_);
165 root_->GetString(kKeyboardLayoutAttr, &keyboard_layout_);
166 root_->GetString(kRegistrationUrlAttr, ®istration_url_);
167
168 std::string hwid;
169 if (system_access->GetMachineStatistic(kHwid, &hwid)) {
170 ListValue* hwid_list = NULL;
171 if (root_->GetList(kHwidMapAttr, &hwid_list)) {
172 for (size_t i = 0; i < hwid_list->GetSize(); ++i) {
173 DictionaryValue* hwid_dictionary = NULL;
174 std::string hwid_mask;
175 if (hwid_list->GetDictionary(i, &hwid_dictionary) &&
176 hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) {
177 if (MatchPattern(hwid, hwid_mask)) {
178 // If HWID for this machine matches some mask, use HWID specific
179 // settings.
180 std::string result;
181 if (hwid_dictionary->GetString(kInitialLocaleAttr, &result))
182 initial_locale_ = result;
183
184 if (hwid_dictionary->GetString(kInitialTimezoneAttr, &result))
185 initial_timezone_ = result;
186
187 if (hwid_dictionary->GetString(kKeyboardLayoutAttr, &result))
188 keyboard_layout_ = result;
189 }
190 // Don't break here to allow other entires to be applied if match.
191 } else {
192 LOG(ERROR) << "Syntax error in customization manifest";
193 }
194 }
195 }
196 } else {
197 LOG(ERROR) << "HWID is missing in machine statistics";
198 }
199
200 system_access->GetMachineStatistic(kInitialLocaleAttr, &initial_locale_);
201 system_access->GetMachineStatistic(kInitialTimezoneAttr, &initial_timezone_);
202 system_access->GetMachineStatistic(kKeyboardLayoutAttr, &keyboard_layout_);
203 }
204
GetHelpPage(const std::string & locale) const205 std::string StartupCustomizationDocument::GetHelpPage(
206 const std::string& locale) const {
207 return GetLocaleSpecificString(locale, kSetupContentAttr, kHelpPageAttr);
208 }
209
GetEULAPage(const std::string & locale) const210 std::string StartupCustomizationDocument::GetEULAPage(
211 const std::string& locale) const {
212 return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
213 }
214
215 // ServicesCustomizationDocument implementation. -------------------------------
216
CarrierDeal(DictionaryValue * deal_dict)217 ServicesCustomizationDocument::CarrierDeal::CarrierDeal(
218 DictionaryValue* deal_dict)
219 : notification_count(0),
220 localized_strings(NULL) {
221 deal_dict->GetString(kDealLocaleAttr, &deal_locale);
222 deal_dict->GetString(kTopUpURLAttr, &top_up_url);
223 deal_dict->GetInteger(kNotificationCountAttr, ¬ification_count);
224 std::string date_string;
225 if (deal_dict->GetString(kDealExpireDateAttr, &date_string)) {
226 if (!base::Time::FromString(ASCIIToWide(date_string).c_str(), &expire_date))
227 LOG(ERROR) << "Error parsing deal_expire_date: " << date_string;
228 }
229 deal_dict->GetDictionary(kLocalizedContentAttr, &localized_strings);
230 }
231
GetLocalizedString(const std::string & locale,const std::string & id) const232 std::string ServicesCustomizationDocument::CarrierDeal::GetLocalizedString(
233 const std::string& locale, const std::string& id) const {
234 std::string result;
235 if (localized_strings) {
236 DictionaryValue* locale_dict = NULL;
237 if (localized_strings->GetDictionary(locale, &locale_dict) &&
238 locale_dict->GetString(id, &result)) {
239 return result;
240 } else if (localized_strings->GetDictionary(kDefaultAttr, &locale_dict) &&
241 locale_dict->GetString(id, &result)) {
242 return result;
243 }
244 }
245 return result;
246 }
247
ServicesCustomizationDocument()248 ServicesCustomizationDocument::ServicesCustomizationDocument()
249 : url_(kServicesCustomizationManifestUrl),
250 initial_locale_(WizardController::GetInitialLocale()) {
251 }
252
ServicesCustomizationDocument(const std::string & manifest,const std::string & initial_locale)253 ServicesCustomizationDocument::ServicesCustomizationDocument(
254 const std::string& manifest, const std::string& initial_locale)
255 : initial_locale_(initial_locale) {
256 LoadManifestFromString(manifest);
257 }
258
259 // static
GetInstance()260 ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() {
261 return Singleton<ServicesCustomizationDocument,
262 DefaultSingletonTraits<ServicesCustomizationDocument> >::get();
263 }
264
265 // static
RegisterPrefs(PrefService * local_state)266 void ServicesCustomizationDocument::RegisterPrefs(PrefService* local_state) {
267 local_state->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
268 }
269
270 // static
WasApplied()271 bool ServicesCustomizationDocument::WasApplied() {
272 PrefService* prefs = g_browser_process->local_state();
273 return prefs->GetBoolean(kServicesCustomizationAppliedPref);
274 }
275
276 // static
SetApplied(bool val)277 void ServicesCustomizationDocument::SetApplied(bool val) {
278 PrefService* prefs = g_browser_process->local_state();
279 prefs->SetBoolean(kServicesCustomizationAppliedPref, val);
280 }
281
StartFetching()282 void ServicesCustomizationDocument::StartFetching() {
283 if (url_.SchemeIsFile()) {
284 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
285 NewRunnableMethod(this,
286 &ServicesCustomizationDocument::ReadFileInBackground,
287 FilePath(url_.path())));
288 } else {
289 StartFileFetch();
290 }
291 }
292
ReadFileInBackground(const FilePath & file)293 void ServicesCustomizationDocument::ReadFileInBackground(const FilePath& file) {
294 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
295
296 std::string manifest;
297 if (file_util::ReadFileToString(file, &manifest)) {
298 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
299 NewRunnableMethod(
300 this,
301 &ServicesCustomizationDocument::LoadManifestFromString,
302 manifest));
303 } else {
304 VLOG(1) << "Failed to load services customization manifest from: "
305 << file.value();
306 }
307 }
308
StartFileFetch()309 void ServicesCustomizationDocument::StartFileFetch() {
310 DCHECK(url_.is_valid());
311 url_fetcher_.reset(new URLFetcher(url_, URLFetcher::GET, this));
312 url_fetcher_->set_request_context(
313 ProfileManager::GetDefaultProfile()->GetRequestContext());
314 url_fetcher_->Start();
315 }
316
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)317 void ServicesCustomizationDocument::OnURLFetchComplete(
318 const URLFetcher* source,
319 const GURL& url,
320 const net::URLRequestStatus& status,
321 int response_code,
322 const ResponseCookies& cookies,
323 const std::string& data) {
324 if (response_code == 200) {
325 LoadManifestFromString(data);
326 } else {
327 NetworkLibrary* network = CrosLibrary::Get()->GetNetworkLibrary();
328 if (!network->Connected() && num_retries_ < kMaxFetchRetries) {
329 num_retries_++;
330 retry_timer_.Start(base::TimeDelta::FromSeconds(kRetriesDelayInSec),
331 this, &ServicesCustomizationDocument::StartFileFetch);
332 return;
333 }
334 LOG(ERROR) << "URL fetch for services customization failed:"
335 << " response code = " << response_code
336 << " URL = " << url.spec();
337 }
338 }
339
ApplyCustomization()340 bool ServicesCustomizationDocument::ApplyCustomization() {
341 // TODO(dpolukhin): apply customized apps, exts and support page.
342 SetApplied(true);
343 return true;
344 }
345
GetInitialStartPage(const std::string & locale) const346 std::string ServicesCustomizationDocument::GetInitialStartPage(
347 const std::string& locale) const {
348 return GetLocaleSpecificString(
349 locale, kAppContentAttr, kInitialStartPageAttr);
350 }
351
GetSupportPage(const std::string & locale) const352 std::string ServicesCustomizationDocument::GetSupportPage(
353 const std::string& locale) const {
354 return GetLocaleSpecificString(
355 locale, kAppContentAttr, kSupportPageAttr);
356 }
357
358 const ServicesCustomizationDocument::CarrierDeal*
GetCarrierDeal(const std::string & carrier_id,bool check_restrictions) const359 ServicesCustomizationDocument::GetCarrierDeal(const std::string& carrier_id,
360 bool check_restrictions) const {
361 CarrierDeals::const_iterator iter = carrier_deals_.find(carrier_id);
362 if (iter != carrier_deals_.end()) {
363 CarrierDeal* deal = iter->second;
364 if (check_restrictions) {
365 // Deal locale has to match initial_locale (= launch country).
366 if (initial_locale_ != deal->deal_locale)
367 return NULL;
368 // Make sure that deal is still active,
369 // i.e. if deal expire date is defined, check it.
370 if (!deal->expire_date.is_null() &&
371 deal->expire_date <= base::Time::Now()) {
372 return NULL;
373 }
374 }
375 return deal;
376 } else {
377 return NULL;
378 }
379 }
380
LoadManifestFromString(const std::string & manifest)381 bool ServicesCustomizationDocument::LoadManifestFromString(
382 const std::string& manifest) {
383 if (!CustomizationDocument::LoadManifestFromString(manifest))
384 return false;
385
386 DictionaryValue* carriers = NULL;
387 if (root_.get() && root_->GetDictionary(kCarrierDealsAttr, &carriers)) {
388 for (DictionaryValue::key_iterator iter = carriers->begin_keys();
389 iter != carriers->end_keys(); ++iter) {
390 DictionaryValue* carrier_deal = NULL;
391 if (carriers->GetDictionary(*iter, &carrier_deal)) {
392 carrier_deals_[*iter] = new CarrierDeal(carrier_deal);
393 }
394 }
395 }
396 return true;
397 }
398
399 } // namespace chromeos
400