customization_document.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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 "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/file_util.h"
10#include "base/files/file_path.h"
11#include "base/json/json_reader.h"
12#include "base/logging.h"
13#include "base/prefs/pref_registry_simple.h"
14#include "base/prefs/pref_service.h"
15#include "base/strings/string_util.h"
16#include "base/strings/utf_string_conversions.h"
17#include "base/time/time.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/chromeos/login/wizard_controller.h"
20#include "chrome/browser/profiles/profile_manager.h"
21#include "chromeos/network/network_state.h"
22#include "chromeos/network/network_state_handler.h"
23#include "chromeos/system/statistics_provider.h"
24#include "content/public/browser/browser_thread.h"
25#include "net/url_request/url_fetcher.h"
26
27using content::BrowserThread;
28
29// Manifest attributes names.
30
31namespace {
32
33const char kVersionAttr[] = "version";
34const char kDefaultAttr[] = "default";
35const char kInitialLocaleAttr[] = "initial_locale";
36const char kInitialTimezoneAttr[] = "initial_timezone";
37const char kKeyboardLayoutAttr[] = "keyboard_layout";
38const char kRegistrationUrlAttr[] = "registration_url";
39const char kHwidMapAttr[] = "hwid_map";
40const char kHwidMaskAttr[] = "hwid_mask";
41const char kSetupContentAttr[] = "setup_content";
42const char kHelpPageAttr[] = "help_page";
43const char kEulaPageAttr[] = "eula_page";
44const char kAppContentAttr[] = "app_content";
45const char kInitialStartPageAttr[] = "initial_start_page";
46const char kSupportPageAttr[] = "support_page";
47
48const char kAcceptedManifestVersion[] = "1.0";
49
50// Path to OEM partner startup customization manifest.
51const char kStartupCustomizationManifestPath[] =
52    "/opt/oem/etc/startup_manifest.json";
53
54// URL where to fetch OEM services customization manifest from.
55const char kServicesCustomizationManifestUrl[] =
56    "file:///opt/oem/etc/services_manifest.json";
57
58// Name of local state option that tracks if services customization has been
59// applied.
60const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied";
61
62// Maximum number of retries to fetch file if network is not available.
63const int kMaxFetchRetries = 3;
64
65// Delay between file fetch retries if network is not available.
66const int kRetriesDelayInSec = 2;
67
68}  // anonymous namespace
69
70namespace chromeos {
71
72// CustomizationDocument implementation. ---------------------------------------
73
74CustomizationDocument::CustomizationDocument(
75    const std::string& accepted_version)
76    : accepted_version_(accepted_version) {}
77
78CustomizationDocument::~CustomizationDocument() {}
79
80bool CustomizationDocument::LoadManifestFromFile(
81    const base::FilePath& manifest_path) {
82  std::string manifest;
83  if (!base::ReadFileToString(manifest_path, &manifest))
84    return false;
85  return LoadManifestFromString(manifest);
86}
87
88bool CustomizationDocument::LoadManifestFromString(
89    const std::string& manifest) {
90  int error_code = 0;
91  std::string error;
92  scoped_ptr<Value> root(base::JSONReader::ReadAndReturnError(manifest,
93      base::JSON_ALLOW_TRAILING_COMMAS, &error_code, &error));
94  if (error_code != base::JSONReader::JSON_NO_ERROR)
95    LOG(ERROR) << error;
96  DCHECK(root.get() != NULL);
97  if (root.get() == NULL)
98    return false;
99  DCHECK(root->GetType() == Value::TYPE_DICTIONARY);
100  if (root->GetType() == Value::TYPE_DICTIONARY) {
101    root_.reset(static_cast<DictionaryValue*>(root.release()));
102    std::string result;
103    if (root_->GetString(kVersionAttr, &result) &&
104        result == accepted_version_)
105      return true;
106
107    LOG(ERROR) << "Wrong customization manifest version";
108    root_.reset(NULL);
109  }
110  return false;
111}
112
113std::string CustomizationDocument::GetLocaleSpecificString(
114    const std::string& locale,
115    const std::string& dictionary_name,
116    const std::string& entry_name) const {
117  DictionaryValue* dictionary_content = NULL;
118  if (!root_.get() ||
119      !root_->GetDictionary(dictionary_name, &dictionary_content))
120    return std::string();
121
122  DictionaryValue* locale_dictionary = NULL;
123  if (dictionary_content->GetDictionary(locale, &locale_dictionary)) {
124    std::string result;
125    if (locale_dictionary->GetString(entry_name, &result))
126      return result;
127  }
128
129  DictionaryValue* default_dictionary = NULL;
130  if (dictionary_content->GetDictionary(kDefaultAttr, &default_dictionary)) {
131    std::string result;
132    if (default_dictionary->GetString(entry_name, &result))
133      return result;
134  }
135
136  return std::string();
137}
138
139// StartupCustomizationDocument implementation. --------------------------------
140
141StartupCustomizationDocument::StartupCustomizationDocument()
142    : CustomizationDocument(kAcceptedManifestVersion) {
143  {
144    // Loading manifest causes us to do blocking IO on UI thread.
145    // Temporarily allow it until we fix http://crosbug.com/11103
146    base::ThreadRestrictions::ScopedAllowIO allow_io;
147    LoadManifestFromFile(base::FilePath(kStartupCustomizationManifestPath));
148  }
149  Init(chromeos::system::StatisticsProvider::GetInstance());
150}
151
152StartupCustomizationDocument::StartupCustomizationDocument(
153    chromeos::system::StatisticsProvider* statistics_provider,
154    const std::string& manifest)
155    : CustomizationDocument(kAcceptedManifestVersion) {
156  LoadManifestFromString(manifest);
157  Init(statistics_provider);
158}
159
160StartupCustomizationDocument::~StartupCustomizationDocument() {}
161
162StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() {
163  return Singleton<StartupCustomizationDocument,
164      DefaultSingletonTraits<StartupCustomizationDocument> >::get();
165}
166
167void StartupCustomizationDocument::Init(
168    chromeos::system::StatisticsProvider* statistics_provider) {
169  if (IsReady()) {
170    root_->GetString(kInitialLocaleAttr, &initial_locale_);
171    root_->GetString(kInitialTimezoneAttr, &initial_timezone_);
172    root_->GetString(kKeyboardLayoutAttr, &keyboard_layout_);
173    root_->GetString(kRegistrationUrlAttr, &registration_url_);
174
175    std::string hwid;
176    if (statistics_provider->GetMachineStatistic(
177            chromeos::system::kHardwareClassKey, &hwid)) {
178      ListValue* hwid_list = NULL;
179      if (root_->GetList(kHwidMapAttr, &hwid_list)) {
180        for (size_t i = 0; i < hwid_list->GetSize(); ++i) {
181          DictionaryValue* hwid_dictionary = NULL;
182          std::string hwid_mask;
183          if (hwid_list->GetDictionary(i, &hwid_dictionary) &&
184              hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) {
185            if (MatchPattern(hwid, hwid_mask)) {
186              // If HWID for this machine matches some mask, use HWID specific
187              // settings.
188              std::string result;
189              if (hwid_dictionary->GetString(kInitialLocaleAttr, &result))
190                initial_locale_ = result;
191
192              if (hwid_dictionary->GetString(kInitialTimezoneAttr, &result))
193                initial_timezone_ = result;
194
195              if (hwid_dictionary->GetString(kKeyboardLayoutAttr, &result))
196                keyboard_layout_ = result;
197            }
198            // Don't break here to allow other entires to be applied if match.
199          } else {
200            LOG(ERROR) << "Syntax error in customization manifest";
201          }
202        }
203      }
204    } else {
205      LOG(ERROR) << "HWID is missing in machine statistics";
206    }
207  }
208
209  // If manifest doesn't exist still apply values from VPD.
210  statistics_provider->GetMachineStatistic(kInitialLocaleAttr,
211                                           &initial_locale_);
212  statistics_provider->GetMachineStatistic(kInitialTimezoneAttr,
213                                           &initial_timezone_);
214  statistics_provider->GetMachineStatistic(kKeyboardLayoutAttr,
215                                           &keyboard_layout_);
216}
217
218std::string StartupCustomizationDocument::GetHelpPage(
219    const std::string& locale) const {
220  return GetLocaleSpecificString(locale, kSetupContentAttr, kHelpPageAttr);
221}
222
223std::string StartupCustomizationDocument::GetEULAPage(
224    const std::string& locale) const {
225  return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
226}
227
228// ServicesCustomizationDocument implementation. -------------------------------
229
230ServicesCustomizationDocument::ServicesCustomizationDocument()
231    : CustomizationDocument(kAcceptedManifestVersion),
232      url_(kServicesCustomizationManifestUrl) {
233}
234
235ServicesCustomizationDocument::ServicesCustomizationDocument(
236    const std::string& manifest)
237    : CustomizationDocument(kAcceptedManifestVersion) {
238  LoadManifestFromString(manifest);
239}
240
241ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
242
243// static
244ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() {
245  return Singleton<ServicesCustomizationDocument,
246      DefaultSingletonTraits<ServicesCustomizationDocument> >::get();
247}
248
249// static
250void ServicesCustomizationDocument::RegisterPrefs(
251    PrefRegistrySimple* registry) {
252  registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
253}
254
255// static
256bool ServicesCustomizationDocument::WasApplied() {
257  PrefService* prefs = g_browser_process->local_state();
258  return prefs->GetBoolean(kServicesCustomizationAppliedPref);
259}
260
261// static
262void ServicesCustomizationDocument::SetApplied(bool val) {
263  PrefService* prefs = g_browser_process->local_state();
264  prefs->SetBoolean(kServicesCustomizationAppliedPref, val);
265}
266
267void ServicesCustomizationDocument::StartFetching() {
268  if (url_.SchemeIsFile()) {
269    BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
270        base::Bind(&ServicesCustomizationDocument::ReadFileInBackground,
271                   base::Unretained(this),  // this class is a singleton.
272                   base::FilePath(url_.path())));
273  } else {
274    StartFileFetch();
275  }
276}
277
278void ServicesCustomizationDocument::ReadFileInBackground(
279    const base::FilePath& file) {
280  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
281
282  std::string manifest;
283  if (base::ReadFileToString(file, &manifest)) {
284    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
285        base::Bind(
286           base::IgnoreResult(
287               &ServicesCustomizationDocument::LoadManifestFromString),
288           base::Unretained(this),  // this class is a singleton.
289           manifest));
290  } else {
291    VLOG(1) << "Failed to load services customization manifest from: "
292            << file.value();
293  }
294}
295
296void ServicesCustomizationDocument::StartFileFetch() {
297  DCHECK(url_.is_valid());
298  url_fetcher_.reset(net::URLFetcher::Create(
299      url_, net::URLFetcher::GET, this));
300  url_fetcher_->SetRequestContext(
301      ProfileManager::GetDefaultProfile()->GetRequestContext());
302  url_fetcher_->Start();
303}
304
305void ServicesCustomizationDocument::OnURLFetchComplete(
306    const net::URLFetcher* source) {
307  if (source->GetResponseCode() == 200) {
308    std::string data;
309    source->GetResponseAsString(&data);
310    LoadManifestFromString(data);
311  } else {
312    const NetworkState* default_network =
313        NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
314    if (default_network && default_network->IsConnectedState() &&
315        num_retries_ < kMaxFetchRetries) {
316      num_retries_++;
317      retry_timer_.Start(FROM_HERE,
318                         base::TimeDelta::FromSeconds(kRetriesDelayInSec),
319                         this, &ServicesCustomizationDocument::StartFileFetch);
320      return;
321    }
322    LOG(ERROR) << "URL fetch for services customization failed:"
323               << " response code = " << source->GetResponseCode()
324               << " URL = " << source->GetURL().spec();
325  }
326}
327
328bool ServicesCustomizationDocument::ApplyCustomization() {
329  // TODO(dpolukhin): apply customized apps, exts and support page.
330  SetApplied(true);
331  return true;
332}
333
334std::string ServicesCustomizationDocument::GetInitialStartPage(
335    const std::string& locale) const {
336  return GetLocaleSpecificString(
337      locale, kAppContentAttr, kInitialStartPageAttr);
338}
339
340std::string ServicesCustomizationDocument::GetSupportPage(
341    const std::string& locale) const {
342  return GetLocaleSpecificString(
343      locale, kAppContentAttr, kSupportPageAttr);
344}
345
346}  // namespace chromeos
347