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/views/about_chrome_view.h"
6
7#if defined(OS_WIN)
8#include <commdlg.h>
9#endif  // defined(OS_WIN)
10
11#include <algorithm>
12#include <string>
13#include <vector>
14
15#include "base/callback.h"
16#include "base/i18n/rtl.h"
17#include "base/string_number_conversions.h"
18#include "base/utf_string_conversions.h"
19#include "base/win/windows_version.h"
20#include "chrome/browser/google/google_util.h"
21#include "chrome/browser/metrics/user_metrics.h"
22#include "chrome/browser/platform_util.h"
23#include "chrome/browser/prefs/pref_service.h"
24#include "chrome/browser/ui/browser_list.h"
25#include "chrome/browser/ui/views/window.h"
26#include "chrome/common/chrome_constants.h"
27#include "chrome/common/chrome_version_info.h"
28#include "chrome/common/pref_names.h"
29#include "chrome/common/url_constants.h"
30#include "chrome/installer/util/browser_distribution.h"
31#include "grit/chromium_strings.h"
32#include "grit/generated_resources.h"
33#include "grit/locale_settings.h"
34#include "grit/theme_resources.h"
35#include "ui/base/l10n/l10n_util.h"
36#include "ui/base/resource/resource_bundle.h"
37#include "ui/gfx/canvas.h"
38#include "views/controls/textfield/textfield.h"
39#include "views/controls/throbber.h"
40#include "views/layout/layout_constants.h"
41#include "views/view_text_utils.h"
42#include "views/widget/widget.h"
43#include "views/window/window.h"
44#include "webkit/glue/webkit_glue.h"
45
46#if defined(OS_WIN)
47#include "base/win/win_util.h"
48#include "chrome/browser/ui/views/restart_message_box.h"
49#include "chrome/installer/util/install_util.h"
50#endif  // defined(OS_WIN)
51
52#if defined(OS_WIN) || defined(OS_CHROMEOS)
53#include "chrome/browser/browser_process.h"
54#endif  // defined(OS_WIN) || defined(OS_CHROMEOS)
55
56namespace {
57// The pixel width of the version text field. Ideally, we'd like to have the
58// bounds set to the edge of the icon. However, the icon is not a view but a
59// part of the background, so we have to hard code the width to make sure
60// the version field doesn't overlap it.
61const int kVersionFieldWidth = 195;
62
63// These are used as placeholder text around the links in the text in the about
64// dialog.
65const wchar_t* kBeginLink = L"BEGIN_LINK";
66const wchar_t* kEndLink = L"END_LINK";
67const wchar_t* kBeginLinkChr = L"BEGIN_LINK_CHR";
68const wchar_t* kBeginLinkOss = L"BEGIN_LINK_OSS";
69const wchar_t* kEndLinkChr = L"END_LINK_CHR";
70const wchar_t* kEndLinkOss = L"END_LINK_OSS";
71
72// The background bitmap used to draw the background color for the About box
73// and the separator line (this is the image we will draw the logo on top of).
74static const SkBitmap* kBackgroundBmp = NULL;
75
76// Returns a substring from |text| between start and end.
77std::wstring StringSubRange(const std::wstring& text, size_t start,
78                            size_t end) {
79  DCHECK(end > start);
80  return text.substr(start, end - start);
81}
82
83}  // namespace
84
85namespace browser {
86
87  // Declared in browser_dialogs.h so that others don't
88  // need to depend on our .h.
89  views::Window* ShowAboutChromeView(gfx::NativeWindow parent,
90                                     Profile* profile) {
91      views::Window* about_chrome_window =
92        browser::CreateViewsWindow(parent,
93        gfx::Rect(),
94        new AboutChromeView(profile));
95      about_chrome_window->Show();
96      return about_chrome_window;
97  }
98
99}  // namespace browser
100
101////////////////////////////////////////////////////////////////////////////////
102// AboutChromeView, public:
103
104AboutChromeView::AboutChromeView(Profile* profile)
105    : profile_(profile),
106      about_dlg_background_logo_(NULL),
107      about_title_label_(NULL),
108      version_label_(NULL),
109#if defined(OS_CHROMEOS)
110      os_version_label_(NULL),
111#endif
112      copyright_label_(NULL),
113      main_text_label_(NULL),
114      main_text_label_height_(0),
115      chromium_url_(NULL),
116      open_source_url_(NULL),
117      terms_of_service_url_(NULL),
118      restart_button_visible_(false),
119      chromium_url_appears_first_(true),
120      text_direction_is_rtl_(false) {
121  DCHECK(profile);
122#if defined(OS_CHROMEOS)
123  loader_.GetVersion(&consumer_,
124                     NewCallback(this, &AboutChromeView::OnOSVersion),
125                     chromeos::VersionLoader::VERSION_FULL);
126#endif
127  Init();
128
129#if defined(OS_WIN) || defined(OS_CHROMEOS)
130  google_updater_ = new GoogleUpdate();
131  google_updater_->set_status_listener(this);
132#endif
133
134  if (kBackgroundBmp == NULL) {
135    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
136    kBackgroundBmp = rb.GetBitmapNamed(IDR_ABOUT_BACKGROUND_COLOR);
137  }
138}
139
140AboutChromeView::~AboutChromeView() {
141#if defined(OS_WIN) || defined(OS_CHROMEOS)
142  // The Google Updater will hold a pointer to us until it reports status, so we
143  // need to let it know that we will no longer be listening.
144  if (google_updater_)
145    google_updater_->set_status_listener(NULL);
146#endif
147}
148
149void AboutChromeView::Init() {
150  text_direction_is_rtl_ = base::i18n::IsRTL();
151  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
152
153  chrome::VersionInfo version_info;
154  if (!version_info.is_valid()) {
155    NOTREACHED() << L"Failed to initialize about window";
156    return;
157  }
158
159  current_version_ = version_info.Version();
160
161  // This code only runs as a result of the user opening the About box so
162  // doing registry access to get the version string modifier should be fine.
163  base::ThreadRestrictions::ScopedAllowIO allow_io;
164  std::string version_modifier = platform_util::GetVersionStringModifier();
165  if (!version_modifier.empty())
166    version_details_ += " " + version_modifier;
167
168#if !defined(GOOGLE_CHROME_BUILD)
169  version_details_ += " (";
170  version_details_ += version_info.LastChange();
171  version_details_ += ")";
172#endif
173
174  // Views we will add to the *parent* of this dialog, since it will display
175  // next to the buttons which we don't draw ourselves.
176  throbber_.reset(new views::Throbber(50, true));
177  throbber_->set_parent_owned(false);
178  throbber_->SetVisible(false);
179
180  SkBitmap* success_image = rb.GetBitmapNamed(IDR_UPDATE_UPTODATE);
181  success_indicator_.SetImage(*success_image);
182  success_indicator_.set_parent_owned(false);
183
184  SkBitmap* update_available_image = rb.GetBitmapNamed(IDR_UPDATE_AVAILABLE);
185  update_available_indicator_.SetImage(*update_available_image);
186  update_available_indicator_.set_parent_owned(false);
187
188  SkBitmap* timeout_image = rb.GetBitmapNamed(IDR_UPDATE_FAIL);
189  timeout_indicator_.SetImage(*timeout_image);
190  timeout_indicator_.set_parent_owned(false);
191
192  update_label_.SetVisible(false);
193  update_label_.set_parent_owned(false);
194
195  // Regular view controls we draw by ourself. First, we add the background
196  // image for the dialog. We have two different background bitmaps, one for
197  // LTR UIs and one for RTL UIs. We load the correct bitmap based on the UI
198  // layout of the view.
199  about_dlg_background_logo_ = new views::ImageView();
200  SkBitmap* about_background_logo = rb.GetBitmapNamed(base::i18n::IsRTL() ?
201      IDR_ABOUT_BACKGROUND_RTL : IDR_ABOUT_BACKGROUND);
202
203  about_dlg_background_logo_->SetImage(*about_background_logo);
204  AddChildView(about_dlg_background_logo_);
205
206  // Add the dialog labels.
207  about_title_label_ = new views::Label(
208      UTF16ToWide(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
209  about_title_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
210      ResourceBundle::BaseFont).DeriveFont(18));
211  about_title_label_->SetColor(SK_ColorBLACK);
212  AddChildView(about_title_label_);
213
214  // This is a text field so people can copy the version number from the dialog.
215  version_label_ = new views::Textfield();
216  version_label_->SetText(ASCIIToUTF16(current_version_ + version_details_));
217  version_label_->SetReadOnly(true);
218  version_label_->RemoveBorder();
219  version_label_->SetTextColor(SK_ColorBLACK);
220  version_label_->SetBackgroundColor(SK_ColorWHITE);
221  version_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
222      ResourceBundle::BaseFont));
223  AddChildView(version_label_);
224
225#if defined(OS_CHROMEOS)
226  os_version_label_ = new views::Textfield(views::Textfield::STYLE_MULTILINE);
227  os_version_label_->SetReadOnly(true);
228  os_version_label_->RemoveBorder();
229  os_version_label_->SetTextColor(SK_ColorBLACK);
230  os_version_label_->SetBackgroundColor(SK_ColorWHITE);
231  os_version_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
232      ResourceBundle::BaseFont));
233  AddChildView(os_version_label_);
234#endif
235
236  // The copyright URL portion of the main label.
237  copyright_label_ = new views::Label(
238      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ABOUT_VERSION_COPYRIGHT)));
239  copyright_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
240  AddChildView(copyright_label_);
241
242  main_text_label_ = new views::Label(L"");
243
244  // Figure out what to write in the main label of the About box.
245  std::wstring text =
246      UTF16ToWide(l10n_util::GetStringUTF16(IDS_ABOUT_VERSION_LICENSE));
247
248  chromium_url_appears_first_ =
249      text.find(kBeginLinkChr) < text.find(kBeginLinkOss);
250
251  size_t link1 = text.find(kBeginLink);
252  DCHECK(link1 != std::wstring::npos);
253  size_t link1_end = text.find(kEndLink, link1);
254  DCHECK(link1_end != std::wstring::npos);
255  size_t link2 = text.find(kBeginLink, link1_end);
256  DCHECK(link2 != std::wstring::npos);
257  size_t link2_end = text.find(kEndLink, link2);
258  DCHECK(link1_end != std::wstring::npos);
259
260  main_label_chunk1_ = text.substr(0, link1);
261  main_label_chunk2_ = StringSubRange(text, link1_end + wcslen(kEndLinkOss),
262                                      link2);
263  main_label_chunk3_ = text.substr(link2_end + wcslen(kEndLinkOss));
264
265  // The Chromium link within the main text of the dialog.
266  chromium_url_ = new views::Link(
267      StringSubRange(text, text.find(kBeginLinkChr) + wcslen(kBeginLinkChr),
268                     text.find(kEndLinkChr)));
269  AddChildView(chromium_url_);
270  chromium_url_->SetController(this);
271
272  // The Open Source link within the main text of the dialog.
273  open_source_url_ = new views::Link(
274      StringSubRange(text, text.find(kBeginLinkOss) + wcslen(kBeginLinkOss),
275                     text.find(kEndLinkOss)));
276  AddChildView(open_source_url_);
277  open_source_url_->SetController(this);
278
279  // Add together all the strings in the dialog for the purpose of calculating
280  // the height of the dialog. The space for the Terms of Service string is not
281  // included (it is added later, if needed).
282  std::wstring full_text = main_label_chunk1_ + chromium_url_->GetText() +
283                           main_label_chunk2_ + open_source_url_->GetText() +
284                           main_label_chunk3_;
285
286  dialog_dimensions_ = views::Window::GetLocalizedContentsSize(
287      IDS_ABOUT_DIALOG_WIDTH_CHARS,
288      IDS_ABOUT_DIALOG_MINIMUM_HEIGHT_LINES);
289
290  // Create a label and add the full text so we can query it for the height.
291  views::Label dummy_text(full_text);
292  dummy_text.SetMultiLine(true);
293  gfx::Font font =
294      ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
295
296  // Add up the height of the various elements on the page.
297  int height = about_background_logo->height() +
298      views::kRelatedControlVerticalSpacing +
299      // Copyright line.
300      font.GetHeight() +
301      // Main label.
302      dummy_text.GetHeightForWidth(
303          dialog_dimensions_.width() - (2 * views::kPanelHorizMargin)) +
304          views::kRelatedControlVerticalSpacing;
305
306#if defined(GOOGLE_CHROME_BUILD)
307  std::vector<size_t> url_offsets;
308  text = UTF16ToWide(l10n_util::GetStringFUTF16(IDS_ABOUT_TERMS_OF_SERVICE,
309                                                string16(),
310                                                string16(),
311                                                &url_offsets));
312
313  main_label_chunk4_ = text.substr(0, url_offsets[0]);
314  main_label_chunk5_ = text.substr(url_offsets[0]);
315
316  // The Terms of Service URL at the bottom.
317  terms_of_service_url_ = new views::Link(
318      UTF16ToWide(l10n_util::GetStringUTF16(IDS_TERMS_OF_SERVICE)));
319  AddChildView(terms_of_service_url_);
320  terms_of_service_url_->SetController(this);
321
322  // Add the Terms of Service line and some whitespace.
323  height += font.GetHeight() + views::kRelatedControlVerticalSpacing;
324#endif
325
326  // Use whichever is greater (the calculated height or the specified minimum
327  // height).
328  dialog_dimensions_.set_height(std::max(height, dialog_dimensions_.height()));
329}
330
331////////////////////////////////////////////////////////////////////////////////
332// AboutChromeView, views::View implementation:
333
334gfx::Size AboutChromeView::GetPreferredSize() {
335  return dialog_dimensions_;
336}
337
338void AboutChromeView::Layout() {
339  gfx::Size panel_size = GetPreferredSize();
340
341  // Background image for the dialog.
342  gfx::Size sz = about_dlg_background_logo_->GetPreferredSize();
343  // Used to position main text below.
344  int background_image_height = sz.height();
345  about_dlg_background_logo_->SetBounds(panel_size.width() - sz.width(), 0,
346                                        sz.width(), sz.height());
347
348  // First label goes to the top left corner.
349  sz = about_title_label_->GetPreferredSize();
350  about_title_label_->SetBounds(
351      views::kPanelHorizMargin, views::kPanelVertMargin,
352      sz.width(), sz.height());
353
354  // Then we have the version number right below it.
355  sz = version_label_->GetPreferredSize();
356  version_label_->SetBounds(views::kPanelHorizMargin,
357                            about_title_label_->y() +
358                                about_title_label_->height() +
359                                views::kRelatedControlVerticalSpacing,
360                            kVersionFieldWidth,
361                            sz.height());
362
363#if defined(OS_CHROMEOS)
364  // Then we have the version number right below it.
365  sz = os_version_label_->GetPreferredSize();
366  os_version_label_->SetBounds(
367      views::kPanelHorizMargin,
368      version_label_->y() +
369          version_label_->height() +
370          views::kRelatedControlVerticalSpacing,
371      kVersionFieldWidth,
372      sz.height());
373#endif
374
375  // For the width of the main text label we want to use up the whole panel
376  // width and remaining height, minus a little margin on each side.
377  int y_pos = background_image_height + views::kRelatedControlVerticalSpacing;
378  sz.set_width(panel_size.width() - 2 * views::kPanelHorizMargin);
379
380  // Draw the text right below the background image.
381  copyright_label_->SetBounds(views::kPanelHorizMargin,
382                              y_pos,
383                              sz.width(),
384                              sz.height());
385
386  // Then the main_text_label.
387  main_text_label_->SetBounds(views::kPanelHorizMargin,
388                              copyright_label_->y() +
389                                  copyright_label_->height(),
390                              sz.width(),
391                              main_text_label_height_);
392
393  // Get the y-coordinate of our parent so we can position the text left of the
394  // buttons at the bottom.
395  gfx::Rect parent_bounds = parent()->GetContentsBounds();
396
397  sz = throbber_->GetPreferredSize();
398  int throbber_topleft_x = views::kPanelHorizMargin;
399  int throbber_topleft_y =
400      parent_bounds.bottom() - sz.height() - views::kButtonVEdgeMargin - 3;
401  throbber_->SetBounds(throbber_topleft_x, throbber_topleft_y,
402                       sz.width(), sz.height());
403
404  // This image is hidden (see ViewHierarchyChanged) and displayed on demand.
405  sz = success_indicator_.GetPreferredSize();
406  success_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y,
407                               sz.width(), sz.height());
408
409  // This image is hidden (see ViewHierarchyChanged) and displayed on demand.
410  sz = update_available_indicator_.GetPreferredSize();
411  update_available_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y,
412                                        sz.width(), sz.height());
413
414  // This image is hidden (see ViewHierarchyChanged) and displayed on demand.
415  sz = timeout_indicator_.GetPreferredSize();
416  timeout_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y,
417                               sz.width(), sz.height());
418
419  // The update label should be at the bottom of the screen, to the right of
420  // the throbber. We specify width to the end of the dialog because it contains
421  // variable length messages.
422  sz = update_label_.GetPreferredSize();
423  int update_label_x = throbber_->x() + throbber_->width() +
424                       views::kRelatedControlHorizontalSpacing;
425  update_label_.SetHorizontalAlignment(views::Label::ALIGN_LEFT);
426  update_label_.SetBounds(update_label_x,
427                          throbber_topleft_y + 1,
428                          parent_bounds.width() - update_label_x,
429                          sz.height());
430}
431
432
433void AboutChromeView::OnPaint(gfx::Canvas* canvas) {
434  views::View::OnPaint(canvas);
435
436  // Draw the background image color (and the separator) across the dialog.
437  // This will become the background for the logo image at the top of the
438  // dialog.
439  canvas->TileImageInt(*kBackgroundBmp, 0, 0,
440                       dialog_dimensions_.width(), kBackgroundBmp->height());
441
442  gfx::Font font =
443      ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
444
445  const gfx::Rect label_bounds = main_text_label_->bounds();
446
447  views::Link* link1 =
448      chromium_url_appears_first_ ? chromium_url_ : open_source_url_;
449  views::Link* link2 =
450      chromium_url_appears_first_ ? open_source_url_ : chromium_url_;
451  gfx::Rect* rect1 = chromium_url_appears_first_ ?
452      &chromium_url_rect_ : &open_source_url_rect_;
453  gfx::Rect* rect2 = chromium_url_appears_first_ ?
454      &open_source_url_rect_ : &chromium_url_rect_;
455
456  // This struct keeps track of where to write the next word (which x,y
457  // pixel coordinate). This struct is updated after drawing text and checking
458  // if we need to wrap.
459  gfx::Size position;
460  // Draw the first text chunk and position the Chromium url.
461  view_text_utils::DrawTextAndPositionUrl(canvas, main_text_label_,
462      main_label_chunk1_, link1, rect1, &position, text_direction_is_rtl_,
463      label_bounds, font);
464  // Draw the second text chunk and position the Open Source url.
465  view_text_utils::DrawTextAndPositionUrl(canvas, main_text_label_,
466      main_label_chunk2_, link2, rect2, &position, text_direction_is_rtl_,
467      label_bounds, font);
468  // Draw the third text chunk (which has no URL associated with it).
469  view_text_utils::DrawTextAndPositionUrl(canvas, main_text_label_,
470      main_label_chunk3_, NULL, NULL, &position, text_direction_is_rtl_,
471      label_bounds, font);
472
473#if defined(GOOGLE_CHROME_BUILD)
474  // Insert a line break and some whitespace.
475  position.set_width(0);
476  position.Enlarge(0, font.GetHeight() + views::kRelatedControlVerticalSpacing);
477
478  // And now the Terms of Service and position the TOS url.
479  view_text_utils::DrawTextAndPositionUrl(canvas, main_text_label_,
480      main_label_chunk4_, terms_of_service_url_, &terms_of_service_url_rect_,
481      &position, text_direction_is_rtl_, label_bounds, font);
482  // The last text chunk doesn't have a URL associated with it.
483  view_text_utils::DrawTextAndPositionUrl(canvas, main_text_label_,
484       main_label_chunk5_, NULL, NULL, &position, text_direction_is_rtl_,
485       label_bounds, font);
486
487  // Position the TOS URL within the main label.
488  terms_of_service_url_->SetBounds(terms_of_service_url_rect_.x(),
489                                   terms_of_service_url_rect_.y(),
490                                   terms_of_service_url_rect_.width(),
491                                   terms_of_service_url_rect_.height());
492#endif
493
494  // Position the URLs within the main label. First position the Chromium URL
495  // within the main label.
496  chromium_url_->SetBounds(chromium_url_rect_.x(),
497                           chromium_url_rect_.y(),
498                           chromium_url_rect_.width(),
499                           chromium_url_rect_.height());
500  // Then position the Open Source URL within the main label.
501  open_source_url_->SetBounds(open_source_url_rect_.x(),
502                              open_source_url_rect_.y(),
503                              open_source_url_rect_.width(),
504                              open_source_url_rect_.height());
505
506  // Save the height so we can set the bounds correctly.
507  main_text_label_height_ = position.height() + font.GetHeight();
508}
509
510void AboutChromeView::ViewHierarchyChanged(bool is_add,
511                                           views::View* parent,
512                                           views::View* child) {
513  // Since we want some of the controls to show up in the same visual row
514  // as the buttons, which are provided by the framework, we must add the
515  // buttons to the non-client view, which is the parent of this view.
516  // Similarly, when we're removed from the view hierarchy, we must take care
517  // to remove these items as well.
518  if (child == this) {
519    if (is_add) {
520      parent->AddChildView(&update_label_);
521      parent->AddChildView(throbber_.get());
522      parent->AddChildView(&success_indicator_);
523      success_indicator_.SetVisible(false);
524      parent->AddChildView(&update_available_indicator_);
525      update_available_indicator_.SetVisible(false);
526      parent->AddChildView(&timeout_indicator_);
527      timeout_indicator_.SetVisible(false);
528
529#if defined(OS_WIN)
530      // On-demand updates for Chrome don't work in Vista RTM when UAC is turned
531      // off. So, in this case we just want the About box to not mention
532      // on-demand updates. Silent updates (in the background) should still
533      // work as before - enabling UAC or installing the latest service pack
534      // for Vista is another option.
535      if (!(base::win::GetVersion() == base::win::VERSION_VISTA &&
536            (base::win::OSInfo::GetInstance()->service_pack().major == 0) &&
537            !base::win::UserAccountControlIsEnabled())) {
538        UpdateStatus(UPGRADE_CHECK_STARTED, GOOGLE_UPDATE_NO_ERROR);
539        // CheckForUpdate(false, ...) means don't upgrade yet.
540        google_updater_->CheckForUpdate(false, window());
541      }
542#elif defined(OS_CHROMEOS)
543      UpdateStatus(UPGRADE_CHECK_STARTED, GOOGLE_UPDATE_NO_ERROR);
544      // CheckForUpdate(false, ...) means don't upgrade yet.
545      google_updater_->CheckForUpdate(false, window());
546#endif
547    } else {
548      parent->RemoveChildView(&update_label_);
549      parent->RemoveChildView(throbber_.get());
550      parent->RemoveChildView(&success_indicator_);
551      parent->RemoveChildView(&update_available_indicator_);
552      parent->RemoveChildView(&timeout_indicator_);
553    }
554  }
555}
556
557////////////////////////////////////////////////////////////////////////////////
558// AboutChromeView, views::DialogDelegate implementation:
559
560std::wstring AboutChromeView::GetDialogButtonLabel(
561    MessageBoxFlags::DialogButton button) const {
562  if (button == MessageBoxFlags::DIALOGBUTTON_OK) {
563    return UTF16ToWide(l10n_util::GetStringUTF16(IDS_RELAUNCH_AND_UPDATE));
564  } else if (button == MessageBoxFlags::DIALOGBUTTON_CANCEL) {
565    if (restart_button_visible_)
566      return UTF16ToWide(l10n_util::GetStringUTF16(IDS_NOT_NOW));
567    // The OK button (which is the default button) has been re-purposed to be
568    // 'Restart Now' so we want the Cancel button should have the label
569    // OK but act like a Cancel button in all other ways.
570    return UTF16ToWide(l10n_util::GetStringUTF16(IDS_OK));
571  }
572
573  NOTREACHED();
574  return L"";
575}
576
577std::wstring AboutChromeView::GetWindowTitle() const {
578  return UTF16ToWide(l10n_util::GetStringUTF16(IDS_ABOUT_CHROME_TITLE));
579}
580
581bool AboutChromeView::IsDialogButtonEnabled(
582    MessageBoxFlags::DialogButton button) const {
583  if (button == MessageBoxFlags::DIALOGBUTTON_OK && !restart_button_visible_)
584    return false;
585
586  return true;
587}
588
589bool AboutChromeView::IsDialogButtonVisible(
590    MessageBoxFlags::DialogButton button) const {
591  if (button == MessageBoxFlags::DIALOGBUTTON_OK && !restart_button_visible_)
592    return false;
593
594  return true;
595}
596
597// (on ChromeOS) the default focus is ending up in the version field when
598// the update button is hidden. This forces the focus to always be on the
599// OK button (which is the dialog cancel button, see GetDialogButtonLabel
600// above).
601int AboutChromeView::GetDefaultDialogButton() const {
602  return MessageBoxFlags::DIALOGBUTTON_CANCEL;
603}
604
605bool AboutChromeView::CanResize() const {
606  return false;
607}
608
609bool AboutChromeView::CanMaximize() const {
610  return false;
611}
612
613bool AboutChromeView::IsAlwaysOnTop() const {
614  return false;
615}
616
617bool AboutChromeView::HasAlwaysOnTopMenu() const {
618  return false;
619}
620
621bool AboutChromeView::IsModal() const {
622  return true;
623}
624
625bool AboutChromeView::Accept() {
626#if defined(OS_WIN) || defined(OS_CHROMEOS)
627  // Set the flag to restore the last session on shutdown.
628  PrefService* pref_service = g_browser_process->local_state();
629  pref_service->SetBoolean(prefs::kRestartLastSessionOnShutdown, true);
630  BrowserList::CloseAllBrowsersAndExit();
631#endif
632
633  return true;
634}
635
636views::View* AboutChromeView::GetContentsView() {
637  return this;
638}
639
640////////////////////////////////////////////////////////////////////////////////
641// AboutChromeView, views::LinkController implementation:
642
643void AboutChromeView::LinkActivated(views::Link* source,
644                                    int event_flags) {
645  GURL url;
646  if (source == terms_of_service_url_) {
647    url = GURL(chrome::kAboutTermsURL);
648  } else if (source == chromium_url_) {
649    url = google_util::AppendGoogleLocaleParam(
650      GURL(chrome::kChromiumProjectURL));
651  } else if (source == open_source_url_) {
652    url = GURL(chrome::kAboutCreditsURL);
653  } else {
654    NOTREACHED() << "Unknown link source";
655  }
656
657  Browser* browser = BrowserList::GetLastActive();
658#if defined(OS_CHROMEOS)
659  browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK);
660#else
661  browser->OpenURL(url, GURL(), NEW_WINDOW, PageTransition::LINK);
662#endif
663}
664
665#if defined(OS_CHROMEOS)
666void AboutChromeView::OnOSVersion(
667    chromeos::VersionLoader::Handle handle,
668    std::string version) {
669
670  // This is a hack to "wrap" the very long Test Build version after
671  // the version number, the remaining text won't be visible but can
672  // be selected, copied, pasted.
673  std::string::size_type pos = version.find(" (Test Build");
674  if (pos != std::string::npos)
675    version.replace(pos, 1, "\n");
676
677  os_version_label_->SetText(UTF8ToUTF16(version));
678}
679#endif
680
681#if defined(OS_WIN) || defined(OS_CHROMEOS)
682////////////////////////////////////////////////////////////////////////////////
683// AboutChromeView, GoogleUpdateStatusListener implementation:
684
685void AboutChromeView::OnReportResults(GoogleUpdateUpgradeResult result,
686                                      GoogleUpdateErrorCode error_code,
687                                      const std::wstring& version) {
688  // Drop the last reference to the object so that it gets cleaned up here.
689  google_updater_ = NULL;
690
691  // Make a note of which version Google Update is reporting is the latest
692  // version.
693  new_version_available_ = version;
694  UpdateStatus(result, error_code);
695}
696////////////////////////////////////////////////////////////////////////////////
697// AboutChromeView, private:
698
699void AboutChromeView::UpdateStatus(GoogleUpdateUpgradeResult result,
700                                   GoogleUpdateErrorCode error_code) {
701#if !defined(GOOGLE_CHROME_BUILD) && !defined(OS_CHROMEOS)
702  // For Chromium builds it would show an error message.
703  // But it looks weird because in fact there is no error,
704  // just the update server is not available for non-official builds.
705  return;
706#endif
707  bool show_success_indicator = false;
708  bool show_update_available_indicator = false;
709  bool show_timeout_indicator = false;
710  bool show_throbber = false;
711  bool show_update_label = true;  // Always visible, except at start.
712
713  switch (result) {
714    case UPGRADE_STARTED:
715      UserMetrics::RecordAction(UserMetricsAction("Upgrade_Started"), profile_);
716      show_throbber = true;
717      update_label_.SetText(
718          UTF16ToWide(l10n_util::GetStringUTF16(IDS_UPGRADE_STARTED)));
719      break;
720    case UPGRADE_CHECK_STARTED:
721      UserMetrics::RecordAction(UserMetricsAction("UpgradeCheck_Started"),
722                                profile_);
723      show_throbber = true;
724      update_label_.SetText(
725          UTF16ToWide(l10n_util::GetStringUTF16(IDS_UPGRADE_CHECK_STARTED)));
726      break;
727    case UPGRADE_IS_AVAILABLE:
728      UserMetrics::RecordAction(
729          UserMetricsAction("UpgradeCheck_UpgradeIsAvailable"), profile_);
730      DCHECK(!google_updater_);  // Should have been nulled out already.
731      google_updater_ = new GoogleUpdate();
732      google_updater_->set_status_listener(this);
733      UpdateStatus(UPGRADE_STARTED, GOOGLE_UPDATE_NO_ERROR);
734      // CheckForUpdate(true,...) means perform upgrade if new version found.
735      google_updater_->CheckForUpdate(true, window());
736      // TODO(seanparent): Need to see if this code needs to change to
737      // force a machine restart.
738      return;
739    case UPGRADE_ALREADY_UP_TO_DATE: {
740      // The extra version check is necessary on Windows because the application
741      // may be already up to date on disk though the running app is still
742      // out of date. Chrome OS doesn't quite have this issue since the
743      // OS/App are updated together. If a newer version of the OS has been
744      // staged then UPGRADE_SUCESSFUL will be returned.
745#if defined(OS_WIN)
746      // Google Update reported that Chrome is up-to-date. Now make sure that we
747      // are running the latest version and if not, notify the user by falling
748      // into the next case of UPGRADE_SUCCESSFUL.
749      BrowserDistribution* dist = BrowserDistribution::GetDistribution();
750      base::ThreadRestrictions::ScopedAllowIO allow_io;
751      scoped_ptr<Version> installed_version(
752          InstallUtil::GetChromeVersion(dist, false));
753      if (!installed_version.get()) {
754        // User-level Chrome is not installed, check system-level.
755        installed_version.reset(InstallUtil::GetChromeVersion(dist, true));
756      }
757      scoped_ptr<Version> running_version(
758          Version::GetVersionFromString(current_version_));
759      if (!installed_version.get() ||
760          (installed_version->CompareTo(*running_version) <= 0)) {
761#endif
762        UserMetrics::RecordAction(
763            UserMetricsAction("UpgradeCheck_AlreadyUpToDate"), profile_);
764#if defined(OS_CHROMEOS)
765        std::wstring update_label_text = UTF16ToWide(l10n_util::GetStringFUTF16(
766            IDS_UPGRADE_ALREADY_UP_TO_DATE,
767            l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
768#else
769        std::wstring update_label_text = l10n_util::GetStringFUTF16(
770            IDS_UPGRADE_ALREADY_UP_TO_DATE,
771            l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
772            ASCIIToUTF16(current_version_));
773#endif
774        if (base::i18n::IsRTL()) {
775          update_label_text.push_back(
776              static_cast<wchar_t>(base::i18n::kLeftToRightMark));
777        }
778        update_label_.SetText(update_label_text);
779        show_success_indicator = true;
780        break;
781#if defined(OS_WIN)
782      }
783#endif
784      // No break here as we want to notify user about upgrade if there is one.
785    }
786    case UPGRADE_SUCCESSFUL: {
787      if (result == UPGRADE_ALREADY_UP_TO_DATE)
788        UserMetrics::RecordAction(
789            UserMetricsAction("UpgradeCheck_AlreadyUpgraded"), profile_);
790      else
791        UserMetrics::RecordAction(UserMetricsAction("UpgradeCheck_Upgraded"),
792                                  profile_);
793      restart_button_visible_ = true;
794      const std::wstring& update_string =
795          UTF16ToWide(l10n_util::GetStringFUTF16(
796              IDS_UPGRADE_SUCCESSFUL_RELAUNCH,
797              l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
798      update_label_.SetText(update_string);
799      show_success_indicator = true;
800      break;
801    }
802    case UPGRADE_ERROR:
803      UserMetrics::RecordAction(UserMetricsAction("UpgradeCheck_Error"),
804                                profile_);
805      restart_button_visible_ = false;
806      if (error_code != GOOGLE_UPDATE_DISABLED_BY_POLICY) {
807        update_label_.SetText(UTF16ToWide(
808            l10n_util::GetStringFUTF16Int(IDS_UPGRADE_ERROR, error_code)));
809      } else {
810        update_label_.SetText(UTF16ToWide(
811            l10n_util::GetStringUTF16(IDS_UPGRADE_DISABLED_BY_POLICY)));
812      }
813      show_timeout_indicator = true;
814      break;
815    default:
816      NOTREACHED();
817  }
818
819  success_indicator_.SetVisible(show_success_indicator);
820  update_available_indicator_.SetVisible(show_update_available_indicator);
821  timeout_indicator_.SetVisible(show_timeout_indicator);
822  update_label_.SetVisible(show_update_label);
823  throbber_->SetVisible(show_throbber);
824  if (show_throbber)
825    throbber_->Start();
826  else
827    throbber_->Stop();
828
829  // We have updated controls on the parent, so we need to update its layout.
830  parent()->Layout();
831
832  // Check button may have appeared/disappeared. We cannot call this during
833  // ViewHierarchyChanged because the |window()| pointer hasn't been set yet.
834  if (window())
835    GetDialogClientView()->UpdateDialogButtons();
836}
837
838#endif
839