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/ui/webui/options/about_page_handler.h"
6
7#include <vector>
8
9#include "base/basictypes.h"
10#include "base/callback.h"
11#include "base/command_line.h"
12#include "base/i18n/time_formatting.h"
13#include "base/string16.h"
14#include "base/string_number_conversions.h"
15#include "base/time.h"
16#include "base/utf_string_conversions.h"
17#include "base/values.h"
18#include "chrome/browser/google/google_util.h"
19#include "chrome/browser/platform_util.h"
20#include "chrome/common/chrome_version_info.h"
21#include "chrome/common/url_constants.h"
22#include "googleurl/src/gurl.h"
23#include "grit/chromium_strings.h"
24#include "grit/generated_resources.h"
25#include "grit/locale_settings.h"
26#include "grit/theme_resources.h"
27#include "ui/base/l10n/l10n_util.h"
28#include "ui/base/resource/resource_bundle.h"
29#include "webkit/glue/webkit_glue.h"
30
31#if defined(CHROME_V8)
32#include "v8/include/v8.h"
33#endif
34
35#if defined(OS_CHROMEOS)
36#include "chrome/browser/chromeos/cros/cros_library.h"
37#include "chrome/browser/chromeos/cros/power_library.h"
38#include "chrome/browser/chromeos/cros/update_library.h"
39#include "chrome/browser/chromeos/login/user_manager.h"
40#include "chrome/browser/chromeos/login/wizard_controller.h"
41#endif
42
43namespace {
44
45// These are used as placeholder text around the links in the text in the
46// license.
47const char kBeginLink[] = "BEGIN_LINK";
48const char kEndLink[] = "END_LINK";
49const char kBeginLinkChr[] = "BEGIN_LINK_CHR";
50const char kBeginLinkOss[] = "BEGIN_LINK_OSS";
51const char kEndLinkChr[] = "END_LINK_CHR";
52const char kEndLinkOss[] = "END_LINK_OSS";
53#if defined(OS_CHROMEOS)
54const char kBeginLinkCrosOss[] = "BEGIN_LINK_CROS_OSS";
55const char kEndLinkCrosOss[] = "END_LINK_CROS_OSS";
56#endif
57
58// Returns a substring [start, end) from |text|.
59std::string StringSubRange(const std::string& text, size_t start,
60                           size_t end) {
61  DCHECK(end > start);
62  return text.substr(start, end - start);
63}
64
65}  // namespace
66
67#if defined(OS_CHROMEOS)
68
69class AboutPageHandler::UpdateObserver
70    : public chromeos::UpdateLibrary::Observer {
71 public:
72  explicit UpdateObserver(AboutPageHandler* handler) : page_handler_(handler) {}
73  virtual ~UpdateObserver() {}
74
75  AboutPageHandler* page_handler() const { return page_handler_; }
76
77 private:
78  virtual void UpdateStatusChanged(chromeos::UpdateLibrary* object) {
79    page_handler_->UpdateStatus(object->status());
80  }
81
82  AboutPageHandler* page_handler_;
83
84  DISALLOW_COPY_AND_ASSIGN(UpdateObserver);
85};
86
87#endif
88
89AboutPageHandler::AboutPageHandler()
90#if defined(OS_CHROMEOS)
91    : progress_(-1),
92      sticky_(false),
93      started_(false)
94#endif
95{}
96
97AboutPageHandler::~AboutPageHandler() {
98#if defined(OS_CHROMEOS)
99  if (update_observer_.get()) {
100    chromeos::CrosLibrary::Get()->GetUpdateLibrary()->
101        RemoveObserver(update_observer_.get());
102  }
103#endif
104}
105
106void AboutPageHandler::GetLocalizedValues(DictionaryValue* localized_strings) {
107  DCHECK(localized_strings);
108
109  static OptionsStringResource resources[] = {
110#if defined (OS_CHROMEOS)
111    { "firmware", IDS_ABOUT_PAGE_FIRMWARE },
112    { "product", IDS_PRODUCT_OS_NAME },
113    { "os", IDS_PRODUCT_OS_NAME },
114    { "loading", IDS_ABOUT_PAGE_LOADING },
115    { "check_now", IDS_ABOUT_PAGE_CHECK_NOW },
116    { "update_status", IDS_UPGRADE_CHECK_STARTED },
117    { "restart_now", IDS_RELAUNCH_AND_UPDATE },
118#else
119    { "product", IDS_PRODUCT_NAME },
120    { "check_now", IDS_ABOUT_CHROME_UPDATE_CHECK },
121#endif
122    { "browser", IDS_PRODUCT_NAME },
123    { "more_info", IDS_ABOUT_PAGE_MORE_INFO },
124    { "copyright", IDS_ABOUT_VERSION_COPYRIGHT },
125    { "channel", IDS_ABOUT_PAGE_CHANNEL },
126    { "stable", IDS_ABOUT_PAGE_CHANNEL_STABLE },
127    { "beta", IDS_ABOUT_PAGE_CHANNEL_BETA },
128    { "dev", IDS_ABOUT_PAGE_CHANNEL_DEVELOPMENT },
129    { "canary", IDS_ABOUT_PAGE_CHANNEL_CANARY },
130    { "channel_warning_header", IDS_ABOUT_PAGE_CHANNEL_WARNING_HEADER },
131    { "channel_warning_text", IDS_ABOUT_PAGE_CHANNEL_WARNING_TEXT },
132    { "user_agent", IDS_ABOUT_VERSION_USER_AGENT },
133    { "command_line", IDS_ABOUT_VERSION_COMMAND_LINE },
134  };
135
136  RegisterStrings(localized_strings, resources, arraysize(resources));
137  RegisterTitle(localized_strings, "aboutPage", IDS_ABOUT_TAB_TITLE);
138
139  // browser version
140
141  chrome::VersionInfo version_info;
142  DCHECK(version_info.is_valid());
143
144  std::string browser_version = version_info.Version();
145  std::string version_modifier = platform_util::GetVersionStringModifier();
146  if (!version_modifier.empty())
147    browser_version += " " + version_modifier;
148
149#if !defined(GOOGLE_CHROME_BUILD)
150  browser_version += " (";
151  browser_version += version_info.LastChange();
152  browser_version += ")";
153#endif
154
155  localized_strings->SetString("browser_version", browser_version);
156
157  // license
158
159  std::string text = l10n_util::GetStringUTF8(IDS_ABOUT_VERSION_LICENSE);
160
161  bool chromium_url_appears_first =
162      text.find(kBeginLinkChr) < text.find(kBeginLinkOss);
163
164  size_t link1 = text.find(kBeginLink);
165  DCHECK(link1 != std::string::npos);
166  size_t link1_end = text.find(kEndLink, link1);
167  DCHECK(link1_end != std::string::npos);
168  size_t link2 = text.find(kBeginLink, link1_end);
169  DCHECK(link2 != std::string::npos);
170  size_t link2_end = text.find(kEndLink, link2);
171  DCHECK(link2_end != std::string::npos);
172
173  localized_strings->SetString("license_content_0", text.substr(0, link1));
174  localized_strings->SetString("license_content_1",
175      StringSubRange(text, link1_end + strlen(kEndLinkOss), link2));
176  localized_strings->SetString("license_content_2",
177      text.substr(link2_end + strlen(kEndLinkOss)));
178
179  // The Chromium link within the main text of the dialog.
180  localized_strings->SetString(chromium_url_appears_first ?
181      "license_link_content_0" : "license_link_content_1",
182      StringSubRange(text,
183                     text.find(kBeginLinkChr) + strlen(kBeginLinkChr),
184                     text.find(kEndLinkChr)));
185  GURL url = google_util::AppendGoogleLocaleParam(
186      GURL(chrome::kChromiumProjectURL));
187  localized_strings->SetString(chromium_url_appears_first ?
188      "license_link_0" : "license_link_1", url.spec());
189
190  // The Open Source link within the main text of the dialog.  We need to use
191  // the chrome:// variant instead of about:credits; the latter will get
192  // rewritten to about:blank.
193  localized_strings->SetString(chromium_url_appears_first ?
194      "license_link_content_1" : "license_link_content_0",
195      StringSubRange(text,
196                     text.find(kBeginLinkOss) + strlen(kBeginLinkOss),
197                     text.find(kEndLinkOss)));
198  localized_strings->SetString(chromium_url_appears_first ?
199      "license_link_1" : "license_link_0", chrome::kChromeUIAboutCreditsURL);
200
201#if defined(OS_CHROMEOS)
202  std::string cros_text =
203      l10n_util::GetStringUTF8(IDS_ABOUT_CROS_VERSION_LICENSE);
204
205  size_t cros_link = cros_text.find(kBeginLinkCrosOss);
206  DCHECK(cros_link != std::string::npos);
207  size_t cros_link_end = cros_text.find(kEndLinkCrosOss, cros_link);
208  DCHECK(cros_link_end != std::string::npos);
209
210  localized_strings->SetString("cros_license_content_0",
211      cros_text.substr(0, cros_link));
212  localized_strings->SetString("cros_license_content_1",
213      cros_text.substr(cros_link_end + strlen(kEndLinkCrosOss)));
214  localized_strings->SetString("cros_license_link_content_0",
215      StringSubRange(cros_text, cros_link + strlen(kBeginLinkCrosOss),
216                     cros_link_end));
217  localized_strings->SetString("cros_license_link_0",
218      chrome::kChromeUIAboutOSCreditsURL);
219#endif
220
221  // webkit
222
223  localized_strings->SetString("webkit_version",
224                               webkit_glue::GetWebKitVersion());
225
226  // javascript
227
228#if defined(CHROME_V8)
229  localized_strings->SetString("js_engine", "V8");
230  localized_strings->SetString("js_engine_version", v8::V8::GetVersion());
231#else
232  localized_strings->SetString("js_engine", "JavaScriptCore");
233  localized_strings->SetString("js_engine_version",
234                               webkit_glue::GetWebKitVersion());
235#endif
236
237  // user agent
238
239  localized_strings->SetString("user_agent_info",
240                               webkit_glue::GetUserAgent(GURL()));
241
242  // command line
243
244#if defined(OS_WIN)
245  localized_strings->SetString("command_line_info",
246      WideToUTF16(CommandLine::ForCurrentProcess()->command_line_string()));
247#elif defined(OS_POSIX)
248  // TODO(viettrungluu): something horrible might happen if there are non-UTF-8
249  // arguments (since |SetString()| requires Unicode).
250  std::string command_line = "";
251  typedef std::vector<std::string> ArgvList;
252  const ArgvList& argv = CommandLine::ForCurrentProcess()->argv();
253  for (ArgvList::const_iterator iter = argv.begin(); iter != argv.end(); iter++)
254    command_line += " " + *iter;
255  localized_strings->SetString("command_line_info", command_line);
256#endif
257}
258
259void AboutPageHandler::RegisterMessages() {
260  web_ui_->RegisterMessageCallback("PageReady",
261      NewCallback(this, &AboutPageHandler::PageReady));
262  web_ui_->RegisterMessageCallback("SetReleaseTrack",
263      NewCallback(this, &AboutPageHandler::SetReleaseTrack));
264
265#if defined(OS_CHROMEOS)
266  web_ui_->RegisterMessageCallback("CheckNow",
267      NewCallback(this, &AboutPageHandler::CheckNow));
268  web_ui_->RegisterMessageCallback("RestartNow",
269      NewCallback(this, &AboutPageHandler::RestartNow));
270#endif
271}
272
273void AboutPageHandler::PageReady(const ListValue* args) {
274#if defined(OS_CHROMEOS)
275  // Version information is loaded from a callback
276  loader_.EnablePlatformVersions(true);
277  loader_.GetVersion(&consumer_,
278                     NewCallback(this, &AboutPageHandler::OnOSVersion),
279                     chromeos::VersionLoader::VERSION_FULL);
280  loader_.GetFirmware(&consumer_,
281                      NewCallback(this, &AboutPageHandler::OnOSFirmware));
282
283  chromeos::UpdateLibrary* update_library =
284      chromeos::CrosLibrary::Get()->GetUpdateLibrary();
285
286  update_observer_.reset(new UpdateObserver(this));
287  update_library->AddObserver(update_observer_.get());
288
289  // Update the WebUI page with the current status. See comments below.
290  UpdateStatus(update_library->status());
291
292  // Initiate update check. UpdateStatus() below will be called when we
293  // get update status via update_observer_. If the update has been
294  // already complete, update_observer_ won't receive a notification.
295  // This is why we manually update the WebUI page above.
296  CheckNow(NULL);
297
298  // Request the channel information. Use the observer to track the about
299  // page handler and ensure it does not get deleted before the callback.
300  update_library->GetReleaseTrack(UpdateSelectedChannel,
301                                  update_observer_.get());
302#endif
303}
304
305void AboutPageHandler::SetReleaseTrack(const ListValue* args) {
306#if defined(OS_CHROMEOS)
307  if (!chromeos::UserManager::Get()->current_user_is_owner()) {
308    LOG(WARNING) << "Non-owner tried to change release track.";
309    return;
310  }
311  const std::string channel = UTF16ToUTF8(ExtractStringValue(args));
312  chromeos::CrosLibrary::Get()->GetUpdateLibrary()->SetReleaseTrack(channel);
313#endif
314}
315
316#if defined(OS_CHROMEOS)
317
318void AboutPageHandler::CheckNow(const ListValue* args) {
319  // Make sure that libcros is loaded and OOBE is complete.
320  if (chromeos::CrosLibrary::Get()->EnsureLoaded() &&
321      (!WizardController::default_controller() ||
322        WizardController::IsDeviceRegistered())) {
323    chromeos::CrosLibrary::Get()->GetUpdateLibrary()->
324        RequestUpdateCheck(NULL,   // no callback
325                           NULL);  // no userdata
326  }
327}
328
329void AboutPageHandler::RestartNow(const ListValue* args) {
330  chromeos::CrosLibrary::Get()->GetPowerLibrary()->RequestRestart();
331}
332
333void AboutPageHandler::UpdateStatus(
334    const chromeos::UpdateLibrary::Status& status) {
335  string16 message;
336  std::string image = "up-to-date";
337  bool enabled = false;
338
339  switch (status.status) {
340    case chromeos::UPDATE_STATUS_IDLE:
341      if (!sticky_) {
342        message = l10n_util::GetStringFUTF16(IDS_UPGRADE_ALREADY_UP_TO_DATE,
343            l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME));
344        enabled = true;
345      }
346      break;
347    case chromeos::UPDATE_STATUS_CHECKING_FOR_UPDATE:
348      message = l10n_util::GetStringUTF16(IDS_UPGRADE_CHECK_STARTED);
349      sticky_ = false;
350      break;
351    case chromeos::UPDATE_STATUS_UPDATE_AVAILABLE:
352      message = l10n_util::GetStringUTF16(IDS_UPDATE_AVAILABLE);
353      started_ = true;
354      break;
355    case chromeos::UPDATE_STATUS_DOWNLOADING:
356    {
357      int progress = static_cast<int>(status.download_progress * 100.0);
358      if (progress != progress_) {
359        progress_ = progress;
360        message = l10n_util::GetStringFUTF16Int(IDS_UPDATE_DOWNLOADING,
361                                                progress_);
362      }
363      started_ = true;
364    }
365      break;
366    case chromeos::UPDATE_STATUS_VERIFYING:
367      message = l10n_util::GetStringUTF16(IDS_UPDATE_VERIFYING);
368      started_ = true;
369      break;
370    case chromeos::UPDATE_STATUS_FINALIZING:
371      message = l10n_util::GetStringUTF16(IDS_UPDATE_FINALIZING);
372      started_ = true;
373      break;
374    case chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT:
375      message = l10n_util::GetStringUTF16(IDS_UPDATE_COMPLETED);
376      image = "available";
377      sticky_ = true;
378      break;
379    default:
380    // case UPDATE_STATUS_ERROR:
381    // case UPDATE_STATUS_REPORTING_ERROR_EVENT:
382
383    // The error is only displayed if we were able to determine an
384    // update was available.
385      if (started_) {
386        message = l10n_util::GetStringUTF16(IDS_UPDATE_ERROR);
387        image = "fail";
388        enabled = true;
389        sticky_ = true;
390        started_ = false;
391      }
392      break;
393  }
394  if (message.size()) {
395    scoped_ptr<Value> update_message(Value::CreateStringValue(message));
396    // "Checking for update..." needs to be shown for a while, so users
397    // can read it, hence insert delay for this.
398    scoped_ptr<Value> insert_delay(Value::CreateBooleanValue(
399        status.status == chromeos::UPDATE_STATUS_CHECKING_FOR_UPDATE));
400    web_ui_->CallJavascriptFunction("AboutPage.updateStatusCallback",
401                                    *update_message, *insert_delay);
402
403    scoped_ptr<Value> enabled_value(Value::CreateBooleanValue(enabled));
404    web_ui_->CallJavascriptFunction("AboutPage.updateEnableCallback",
405                                    *enabled_value);
406
407    scoped_ptr<Value> image_string(Value::CreateStringValue(image));
408    web_ui_->CallJavascriptFunction("AboutPage.setUpdateImage",
409                                    *image_string);
410  }
411  // We'll change the "Check For Update" button to "Restart" button.
412  if (status.status == chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT) {
413    web_ui_->CallJavascriptFunction("AboutPage.changeToRestartButton");
414  }
415}
416
417void AboutPageHandler::OnOSVersion(chromeos::VersionLoader::Handle handle,
418                                   std::string version) {
419  if (version.size()) {
420    scoped_ptr<Value> version_string(Value::CreateStringValue(version));
421    web_ui_->CallJavascriptFunction("AboutPage.updateOSVersionCallback",
422                                    *version_string);
423  }
424}
425
426void AboutPageHandler::OnOSFirmware(chromeos::VersionLoader::Handle handle,
427                                    std::string firmware) {
428  if (firmware.size()) {
429    scoped_ptr<Value> firmware_string(Value::CreateStringValue(firmware));
430    web_ui_->CallJavascriptFunction("AboutPage.updateOSFirmwareCallback",
431                                    *firmware_string);
432  }
433}
434
435// Callback from UpdateEngine with channel information.
436// static
437void AboutPageHandler::UpdateSelectedChannel(void* user_data,
438                                             const char* channel) {
439  if (!user_data || !channel) {
440    LOG(WARNING) << "UpdateSelectedChannel returned NULL.";
441    return;
442  }
443  UpdateObserver* observer = static_cast<UpdateObserver*>(user_data);
444  if (chromeos::CrosLibrary::Get()->GetUpdateLibrary()->HasObserver(observer)) {
445    // If UpdateLibrary still has the observer, then the page handler is valid.
446    AboutPageHandler* handler = observer->page_handler();
447    scoped_ptr<Value> channel_string(Value::CreateStringValue(channel));
448    handler->web_ui_->CallJavascriptFunction(
449        "AboutPage.updateSelectedOptionCallback", *channel_string);
450  }
451}
452
453#endif  // defined(OS_CHROMEOS)
454