// Copyright (c) 2009 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/views/about_chrome_view.h"

#include "app/gfx/canvas.h"
#include "app/gfx/color_utils.h"
#include "base/i18n/word_iterator.h"
#include "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "base/file_version_info.h"
#include "base/string_util.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/url_constants.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/theme_resources.h"
#include "views/controls/textfield/textfield.h"
#include "views/controls/throbber.h"
#include "views/standard_layout.h"
#include "views/widget/widget.h"
#include "views/window/window.h"
#include "webkit/glue/webkit_glue.h"

#if defined(OS_WIN)
#include <commdlg.h>

#include "base/win_util.h"
#include "chrome/browser/views/restart_message_box.h"
#include "chrome/installer/util/install_util.h"
#endif

namespace {
// The pixel width of the version text field. Ideally, we'd like to have the
// bounds set to the edge of the icon. However, the icon is not a view but a
// part of the background, so we have to hard code the width to make sure
// the version field doesn't overlap it.
const int kVersionFieldWidth = 195;

// These are used as placeholder text around the links in the text in the about
// dialog.
const wchar_t* kBeginLink = L"BEGIN_LINK";
const wchar_t* kEndLink = L"END_LINK";
const wchar_t* kBeginLinkChr = L"BEGIN_LINK_CHR";
const wchar_t* kBeginLinkOss = L"BEGIN_LINK_OSS";
const wchar_t* kEndLinkChr = L"END_LINK_CHR";
const wchar_t* kEndLinkOss = L"END_LINK_OSS";

// The background bitmap used to draw the background color for the About box
// and the separator line (this is the image we will draw the logo on top of).
static const SkBitmap* kBackgroundBmp = NULL;

// Returns a substring from |text| between start and end.
std::wstring StringSubRange(const std::wstring& text, size_t start,
                            size_t end) {
  DCHECK(end > start);
  return text.substr(start, end - start);
}

}  // namespace

namespace browser {

// Declared in browser_dialogs.h so that others don't need to depend on our .h.
void ShowAboutChromeView(gfx::NativeWindow parent,
                         Profile* profile) {
  views::Window::CreateChromeWindow(parent,
                                    gfx::Rect(),
                                    new AboutChromeView(profile))->Show();
}

}  // namespace browser

////////////////////////////////////////////////////////////////////////////////
// AboutChromeView, public:

AboutChromeView::AboutChromeView(Profile* profile)
    : profile_(profile),
      about_dlg_background_logo_(NULL),
      about_title_label_(NULL),
      version_label_(NULL),
#if defined(OS_CHROMEOS)
      os_version_label_(NULL),
#endif
      copyright_label_(NULL),
      main_text_label_(NULL),
      main_text_label_height_(0),
      chromium_url_(NULL),
      open_source_url_(NULL),
      terms_of_service_url_(NULL),
      check_button_status_(CHECKBUTTON_HIDDEN),
      chromium_url_appears_first_(true),
      text_direction_is_rtl_(false) {
  DCHECK(profile);
#if defined(OS_CHROMEOS)
  loader_.GetVersion(&consumer_,
      NewCallback(this, &AboutChromeView::OnOSVersion));
#endif
  Init();

#if defined(OS_WIN)
  google_updater_ = new GoogleUpdate();
  google_updater_->AddStatusChangeListener(this);
#endif

  if (kBackgroundBmp == NULL) {
    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    kBackgroundBmp = rb.GetBitmapNamed(IDR_ABOUT_BACKGROUND_COLOR);
  }
}

AboutChromeView::~AboutChromeView() {
#if defined(OS_WIN)
  // The Google Updater will hold a pointer to us until it reports status, so we
  // need to let it know that we will no longer be listening.
  if (google_updater_)
    google_updater_->RemoveStatusChangeListener();
#endif
}

void AboutChromeView::Init() {
  text_direction_is_rtl_ =
      l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT;
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();

  scoped_ptr<FileVersionInfo> version_info(
      FileVersionInfo::CreateFileVersionInfoForCurrentModule());
  if (version_info.get() == NULL) {
    NOTREACHED() << L"Failed to initialize about window";
    return;
  }

  current_version_ = version_info->file_version();
#if !defined(GOOGLE_CHROME_BUILD)
  current_version_ += L" (";
  current_version_ += version_info->last_change();
  current_version_ += L")";
#endif

  // Views we will add to the *parent* of this dialog, since it will display
  // next to the buttons which we don't draw ourselves.
  throbber_.reset(new views::Throbber(50, true));
  throbber_->set_parent_owned(false);
  throbber_->SetVisible(false);

  SkBitmap* success_image = rb.GetBitmapNamed(IDR_UPDATE_UPTODATE);
  success_indicator_.SetImage(*success_image);
  success_indicator_.set_parent_owned(false);

  SkBitmap* update_available_image = rb.GetBitmapNamed(IDR_UPDATE_AVAILABLE);
  update_available_indicator_.SetImage(*update_available_image);
  update_available_indicator_.set_parent_owned(false);

  SkBitmap* timeout_image = rb.GetBitmapNamed(IDR_UPDATE_FAIL);
  timeout_indicator_.SetImage(*timeout_image);
  timeout_indicator_.set_parent_owned(false);

  update_label_.SetVisible(false);
  update_label_.set_parent_owned(false);

  // Regular view controls we draw by ourself. First, we add the background
  // image for the dialog. We have two different background bitmaps, one for
  // LTR UIs and one for RTL UIs. We load the correct bitmap based on the UI
  // layout of the view.
  about_dlg_background_logo_ = new views::ImageView();
  SkBitmap* about_background_logo;
  if (UILayoutIsRightToLeft())
    about_background_logo = rb.GetBitmapNamed(IDR_ABOUT_BACKGROUND_RTL);
  else
    about_background_logo = rb.GetBitmapNamed(IDR_ABOUT_BACKGROUND);

  about_dlg_background_logo_->SetImage(*about_background_logo);
  AddChildView(about_dlg_background_logo_);

  // Add the dialog labels.
  about_title_label_ = new views::Label(
      l10n_util::GetString(IDS_PRODUCT_NAME));
  about_title_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
      ResourceBundle::BaseFont).DeriveFont(18));
  about_title_label_->SetColor(SK_ColorBLACK);
  AddChildView(about_title_label_);

  // This is a text field so people can copy the version number from the dialog.
  version_label_ = new views::Textfield();
  version_label_->SetText(WideToUTF16Hack(current_version_));
  version_label_->SetReadOnly(true);
  version_label_->RemoveBorder();
  version_label_->SetTextColor(SK_ColorBLACK);
  version_label_->SetBackgroundColor(SK_ColorWHITE);
  version_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
      ResourceBundle::BaseFont));
  AddChildView(version_label_);

#if defined(OS_CHROMEOS)
  os_version_label_ = new views::Textfield();
  os_version_label_->SetReadOnly(true);
  os_version_label_->RemoveBorder();
  os_version_label_->SetTextColor(SK_ColorBLACK);
  os_version_label_->SetBackgroundColor(SK_ColorWHITE);
  os_version_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
      ResourceBundle::BaseFont));
  AddChildView(os_version_label_);
#endif

  // The copyright URL portion of the main label.
  copyright_label_ = new views::Label(
      l10n_util::GetString(IDS_ABOUT_VERSION_COPYRIGHT));
  copyright_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  AddChildView(copyright_label_);

  main_text_label_ = new views::Label(L"");

  // Figure out what to write in the main label of the About box.
  std::wstring text = l10n_util::GetString(IDS_ABOUT_VERSION_LICENSE);

  chromium_url_appears_first_ =
      text.find(kBeginLinkChr) < text.find(kBeginLinkOss);

  size_t link1 = text.find(kBeginLink);
  DCHECK(link1 != std::wstring::npos);
  size_t link1_end = text.find(kEndLink, link1);
  DCHECK(link1_end != std::wstring::npos);
  size_t link2 = text.find(kBeginLink, link1_end);
  DCHECK(link2 != std::wstring::npos);
  size_t link2_end = text.find(kEndLink, link2);
  DCHECK(link1_end != std::wstring::npos);

  main_label_chunk1_ = text.substr(0, link1);
  main_label_chunk2_ = StringSubRange(text, link1_end + wcslen(kEndLinkOss),
                                      link2);
  main_label_chunk3_ = text.substr(link2_end + wcslen(kEndLinkOss));

  // The Chromium link within the main text of the dialog.
  chromium_url_ = new views::Link(
      StringSubRange(text, text.find(kBeginLinkChr) + wcslen(kBeginLinkChr),
                     text.find(kEndLinkChr)));
  AddChildView(chromium_url_);
  chromium_url_->SetController(this);

  // The Open Source link within the main text of the dialog.
  open_source_url_ = new views::Link(
      StringSubRange(text, text.find(kBeginLinkOss) + wcslen(kBeginLinkOss),
                     text.find(kEndLinkOss)));
  AddChildView(open_source_url_);
  open_source_url_->SetController(this);

  // Add together all the strings in the dialog for the purpose of calculating
  // the height of the dialog. The space for the Terms of Service string is not
  // included (it is added later, if needed).
  std::wstring full_text = main_label_chunk1_ + chromium_url_->GetText() +
                           main_label_chunk2_ + open_source_url_->GetText() +
                           main_label_chunk3_;

  dialog_dimensions_ = views::Window::GetLocalizedContentsSize(
      IDS_ABOUT_DIALOG_WIDTH_CHARS,
      IDS_ABOUT_DIALOG_MINIMUM_HEIGHT_LINES);

  // Create a label and add the full text so we can query it for the height.
  views::Label dummy_text(full_text);
  dummy_text.SetMultiLine(true);
  gfx::Font font =
      ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);

  // Add up the height of the various elements on the page.
  int height = about_background_logo->height() +
               kRelatedControlVerticalSpacing +
               // Copyright line.
               font.height() +
               // Main label.
               dummy_text.GetHeightForWidth(
                   dialog_dimensions_.width() - (2 * kPanelHorizMargin)) +
               kRelatedControlVerticalSpacing;

#if defined(GOOGLE_CHROME_BUILD)
  std::vector<size_t> url_offsets;
  text = l10n_util::GetStringF(IDS_ABOUT_TERMS_OF_SERVICE,
                               std::wstring(),
                               std::wstring(),
                               &url_offsets);

  main_label_chunk4_ = text.substr(0, url_offsets[0]);
  main_label_chunk5_ = text.substr(url_offsets[0]);

  // The Terms of Service URL at the bottom.
  terms_of_service_url_ =
      new views::Link(l10n_util::GetString(IDS_TERMS_OF_SERVICE));
  AddChildView(terms_of_service_url_);
  terms_of_service_url_->SetController(this);

  // Add the Terms of Service line and some whitespace.
  height += font.height() + kRelatedControlVerticalSpacing;
#endif

  // Use whichever is greater (the calculated height or the specified minimum
  // height).
  dialog_dimensions_.set_height(std::max(height, dialog_dimensions_.height()));
}

////////////////////////////////////////////////////////////////////////////////
// AboutChromeView, views::View implementation:

gfx::Size AboutChromeView::GetPreferredSize() {
  return dialog_dimensions_;
}

void AboutChromeView::Layout() {
  gfx::Size panel_size = GetPreferredSize();

  // Background image for the dialog.
  gfx::Size sz = about_dlg_background_logo_->GetPreferredSize();
  // Used to position main text below.
  int background_image_height = sz.height();
  about_dlg_background_logo_->SetBounds(panel_size.width() - sz.width(), 0,
                                        sz.width(), sz.height());

  // First label goes to the top left corner.
  sz = about_title_label_->GetPreferredSize();
  about_title_label_->SetBounds(kPanelHorizMargin, kPanelVertMargin,
                                sz.width(), sz.height());

  // Then we have the version number right below it.
  sz = version_label_->GetPreferredSize();
  version_label_->SetBounds(kPanelHorizMargin,
                            about_title_label_->y() +
                                about_title_label_->height() +
                                kRelatedControlVerticalSpacing,
                            kVersionFieldWidth,
                            sz.height());

#if defined(OS_CHROMEOS)
  // Then we have the version number right below it.
  sz = os_version_label_->GetPreferredSize();
  os_version_label_->SetBounds(
      kPanelHorizMargin,
      version_label_->y() +
          version_label_->height() +
          kRelatedControlVerticalSpacing,
      kVersionFieldWidth,
      sz.height());
#endif

  // For the width of the main text label we want to use up the whole panel
  // width and remaining height, minus a little margin on each side.
  int y_pos = background_image_height + kRelatedControlVerticalSpacing;
  sz.set_width(panel_size.width() - 2 * kPanelHorizMargin);

  // Draw the text right below the background image.
  copyright_label_->SetBounds(kPanelHorizMargin,
                              y_pos,
                              sz.width(),
                              sz.height());

  // Then the main_text_label.
  main_text_label_->SetBounds(kPanelHorizMargin,
                              copyright_label_->y() +
                                  copyright_label_->height(),
                              sz.width(),
                              main_text_label_height_);

  // Get the y-coordinate of our parent so we can position the text left of the
  // buttons at the bottom.
  gfx::Rect parent_bounds = GetParent()->GetLocalBounds(false);

  sz = throbber_->GetPreferredSize();
  int throbber_topleft_x = kPanelHorizMargin;
  int throbber_topleft_y = parent_bounds.bottom() - sz.height() -
                           kButtonVEdgeMargin - 3;
  throbber_->SetBounds(throbber_topleft_x, throbber_topleft_y,
                       sz.width(), sz.height());

  // This image is hidden (see ViewHierarchyChanged) and displayed on demand.
  sz = success_indicator_.GetPreferredSize();
  success_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y,
                               sz.width(), sz.height());

  // This image is hidden (see ViewHierarchyChanged) and displayed on demand.
  sz = update_available_indicator_.GetPreferredSize();
  update_available_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y,
                                        sz.width(), sz.height());

  // This image is hidden (see ViewHierarchyChanged) and displayed on demand.
  sz = timeout_indicator_.GetPreferredSize();
  timeout_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y,
                               sz.width(), sz.height());

  // The update label should be at the bottom of the screen, to the right of
  // the throbber. We specify width to the end of the dialog because it contains
  // variable length messages.
  sz = update_label_.GetPreferredSize();
  int update_label_x = throbber_->x() + throbber_->width() +
                       kRelatedControlHorizontalSpacing;
  update_label_.SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  update_label_.SetBounds(update_label_x,
                          throbber_topleft_y + 1,
                          parent_bounds.width() - update_label_x,
                          sz.height());
}


void AboutChromeView::Paint(gfx::Canvas* canvas) {
  views::View::Paint(canvas);

  // Draw the background image color (and the separator) across the dialog.
  // This will become the background for the logo image at the top of the
  // dialog.
  canvas->TileImageInt(*kBackgroundBmp, 0, 0,
                       dialog_dimensions_.width(), kBackgroundBmp->height());

  gfx::Font font =
      ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);

  const gfx::Rect label_bounds = main_text_label_->bounds();

  views::Link* link1 =
      chromium_url_appears_first_ ? chromium_url_ : open_source_url_;
  views::Link* link2 =
      chromium_url_appears_first_ ? open_source_url_ : chromium_url_;
  gfx::Rect* rect1 = chromium_url_appears_first_ ?
      &chromium_url_rect_ : &open_source_url_rect_;
  gfx::Rect* rect2 = chromium_url_appears_first_ ?
      &open_source_url_rect_ : &chromium_url_rect_;

  // This struct keeps track of where to write the next word (which x,y
  // pixel coordinate). This struct is updated after drawing text and checking
  // if we need to wrap.
  gfx::Size position;
  // Draw the first text chunk and position the Chromium url.
  DrawTextAndPositionUrl(canvas, main_label_chunk1_, link1,
                         rect1, &position, label_bounds, font);
  // Draw the second text chunk and position the Open Source url.
  DrawTextAndPositionUrl(canvas, main_label_chunk2_, link2,
                         rect2, &position, label_bounds, font);
  // Draw the third text chunk (which has no URL associated with it).
  DrawTextAndPositionUrl(canvas, main_label_chunk3_, NULL, NULL, &position,
                         label_bounds, font);

#if defined(GOOGLE_CHROME_BUILD)
  // Insert a line break and some whitespace.
  position.set_width(0);
  position.Enlarge(0, font.height() + kRelatedControlVerticalSpacing);

  // And now the Terms of Service and position the TOS url.
  DrawTextAndPositionUrl(canvas, main_label_chunk4_, terms_of_service_url_,
                         &terms_of_service_url_rect_, &position, label_bounds,
                         font);
  // The last text chunk doesn't have a URL associated with it.
  DrawTextAndPositionUrl(canvas, main_label_chunk5_, NULL, NULL, &position,
                         label_bounds, font);

  // Position the TOS URL within the main label.
  terms_of_service_url_->SetBounds(terms_of_service_url_rect_.x(),
                                   terms_of_service_url_rect_.y(),
                                   terms_of_service_url_rect_.width(),
                                   terms_of_service_url_rect_.height());
#endif

  // Position the URLs within the main label. First position the Chromium URL
  // within the main label.
  chromium_url_->SetBounds(chromium_url_rect_.x(),
                           chromium_url_rect_.y(),
                           chromium_url_rect_.width(),
                           chromium_url_rect_.height());
  // Then position the Open Source URL within the main label.
  open_source_url_->SetBounds(open_source_url_rect_.x(),
                              open_source_url_rect_.y(),
                              open_source_url_rect_.width(),
                              open_source_url_rect_.height());

  // Save the height so we can set the bounds correctly.
  main_text_label_height_ = position.height() + font.height();
}

void AboutChromeView::DrawTextAndPositionUrl(gfx::Canvas* canvas,
                                             const std::wstring& text,
                                             views::Link* link,
                                             gfx::Rect* rect,
                                             gfx::Size* position,
                                             const gfx::Rect& bounds,
                                             const gfx::Font& font) {
  DCHECK(canvas && position);

  // What we get passed in as |text| is potentially a mix of LTR and RTL "runs"
  // (a run is a sequence of words that share the same directionality). We
  // initialize a bidirectional ICU line iterator and split the text into runs
  // that are either strictly LTR or strictly RTL (and do not contain a mix).
  l10n_util::BiDiLineIterator bidi_line;
  if (!bidi_line.Open(text.c_str(), true, false))
    return;

  // Iterate over each run and draw it.
  int run_start = 0;
  int run_end = 0;
  const int runs = bidi_line.CountRuns();
  for (int run = 0; run < runs; ++run) {
    UBiDiLevel level = 0;
    bidi_line.GetLogicalRun(run_start, &run_end, &level);
    std::wstring fragment = StringSubRange(text, run_start, run_end);

    // A flag that tells us whether we found LTR text inside RTL text.
    bool ltr_inside_rtl_text =
        ((level & 1) == UBIDI_LTR) && text_direction_is_rtl_;

    // Draw the text chunk contained in |fragment|. |position| is relative to
    // the top left corner of the label we draw inside (also when drawing RTL).
    DrawTextStartingFrom(canvas, fragment, position, bounds, font,
                         ltr_inside_rtl_text);

    run_start = run_end;  // Advance over what we just drew.
  }

  // If the caller is interested in placing a link after this text blurb, we
  // figure out here where to place it.
  if (link && rect) {
    gfx::Size sz = link->GetPreferredSize();
    gfx::Insets insets = link->GetInsets();
    WrapIfWordDoesntFit(sz.width(), font.height(), position, bounds);
    int x = position->width();
    int y = position->height();

    // Links have a border to allow them to be focused.
    y -= insets.top();

    *rect = gfx::Rect(x, y, sz.width(), sz.height());

    // Go from relative pixel coordinates (within the label we are drawing on)
    // to absolute pixel coordinates (relative to the top left corner of the
    // dialog content).
    rect->Offset(bounds.x(), bounds.y());
    // And leave some space to draw the link in.
    position->Enlarge(sz.width(), 0);
  }
}

void AboutChromeView::DrawTextStartingFrom(gfx::Canvas* canvas,
                                           const std::wstring& text,
                                           gfx::Size* position,
                                           const gfx::Rect& bounds,
                                           const gfx::Font& font,
                                           bool ltr_within_rtl) {
#if defined(OS_WIN)
  const SkColor text_color = color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
#else
  // TODO(beng): source from theme provider.
  const SkColor text_color = SK_ColorBLACK;
#endif

  // Iterate through line breaking opportunities (which in English would be
  // spaces and such. This tells us where to wrap.
  WordIterator iter(text, WordIterator::BREAK_LINE);
  if (!iter.Init())
    return;

  int flags = (text_direction_is_rtl_ ?
                   gfx::Canvas::TEXT_ALIGN_RIGHT :
                   gfx::Canvas::TEXT_ALIGN_LEFT) |
              gfx::Canvas::MULTI_LINE |
              gfx::Canvas::HIDE_PREFIX;

  // Iterate over each word in the text, or put in a more locale-neutral way:
  // iterate to the next line breaking opportunity.
  while (iter.Advance()) {
    // Get the word and figure out the dimensions.
    std::wstring word;
    if (!ltr_within_rtl)
      word = iter.GetWord();  // Get the next word.
    else
      word = text;  // Draw the whole text at once.

    int w = font.GetStringWidth(word), h = font.height();
    canvas->SizeStringInt(word, font, &w, &h, flags);

    // If we exceed the boundaries, we need to wrap.
    WrapIfWordDoesntFit(w, font.height(), position, bounds);

    int x = main_text_label_->MirroredXCoordinateInsideView(position->width()) +
            bounds.x();
    if (text_direction_is_rtl_) {
      x -= w;
      // When drawing LTR strings inside RTL text we need to make sure we draw
      // the trailing space (if one exists after the LTR text) on the left of
      // the LTR string.
      if (ltr_within_rtl && word[word.size() - 1] == L' ') {
        int space_w = font.GetStringWidth(L" "), space_h = font.height();
        canvas->SizeStringInt(L" ", font, &space_w, &space_h, flags);
        x += space_w;
      }
    }
    int y = position->height() + bounds.y();

    // Draw the text on the screen (mirrored, if RTL run).
    canvas->DrawStringInt(
        word, font, text_color, x, y, w, font.height(), flags);

    if (word.size() > 0 && word[word.size() - 1] == L'\x0a') {
      // When we come across '\n', we move to the beginning of the next line.
      position->set_width(0);
      position->Enlarge(0, font.height());
    } else {
      // Otherwise, we advance position to the next word.
      position->Enlarge(w, 0);
    }

    if (ltr_within_rtl)
      break;  // LTR within RTL is drawn as one unit, so we are done.
  }
}

void AboutChromeView::WrapIfWordDoesntFit(int word_width,
                                          int font_height,
                                          gfx::Size* position,
                                          const gfx::Rect& bounds) {
  if (position->width() + word_width > bounds.right()) {
    position->set_width(0);
    position->Enlarge(0, font_height);
  }
}

void AboutChromeView::ViewHierarchyChanged(bool is_add,
                                           views::View* parent,
                                           views::View* child) {
  // Since we want the some of the controls to show up in the same visual row
  // as the buttons, which are provided by the framework, we must add the
  // buttons to the non-client view, which is the parent of this view.
  // Similarly, when we're removed from the view hierarchy, we must take care
  // to remove these items as well.
  if (child == this) {
    if (is_add) {
      parent->AddChildView(&update_label_);
      parent->AddChildView(throbber_.get());
      parent->AddChildView(&success_indicator_);
      success_indicator_.SetVisible(false);
      parent->AddChildView(&update_available_indicator_);
      update_available_indicator_.SetVisible(false);
      parent->AddChildView(&timeout_indicator_);
      timeout_indicator_.SetVisible(false);

#if defined (OS_WIN)
      // On-demand updates for Chrome don't work in Vista RTM when UAC is turned
      // off. So, in this case we just want the About box to not mention
      // on-demand updates. Silent updates (in the background) should still
      // work as before - enabling UAC or installing the latest service pack
      // for Vista is another option.
      int service_pack_major = 0, service_pack_minor = 0;
      win_util::GetServicePackLevel(&service_pack_major, &service_pack_minor);
      if (win_util::UserAccountControlIsEnabled() ||
          win_util::GetWinVersion() == win_util::WINVERSION_XP ||
          (win_util::GetWinVersion() == win_util::WINVERSION_VISTA &&
           service_pack_major >= 1) ||
          win_util::GetWinVersion() > win_util::WINVERSION_VISTA) {
        UpdateStatus(UPGRADE_CHECK_STARTED, GOOGLE_UPDATE_NO_ERROR);
        // CheckForUpdate(false, ...) means don't upgrade yet.
        google_updater_->CheckForUpdate(false, window());
      }
#endif
    } else {
      parent->RemoveChildView(&update_label_);
      parent->RemoveChildView(throbber_.get());
      parent->RemoveChildView(&success_indicator_);
      parent->RemoveChildView(&update_available_indicator_);
      parent->RemoveChildView(&timeout_indicator_);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
// AboutChromeView, views::DialogDelegate implementation:

std::wstring AboutChromeView::GetDialogButtonLabel(
    MessageBoxFlags::DialogButton button) const {
  if (button == MessageBoxFlags::DIALOGBUTTON_OK) {
    return l10n_util::GetString(IDS_ABOUT_CHROME_UPDATE_CHECK);
  } else if (button == MessageBoxFlags::DIALOGBUTTON_CANCEL) {
    // The OK button (which is the default button) has been re-purposed to be
    // 'Check for Updates' so we want the Cancel button should have the label
    // OK but act like a Cancel button in all other ways.
    return l10n_util::GetString(IDS_OK);
  }

  NOTREACHED();
  return L"";
}

bool AboutChromeView::IsDialogButtonEnabled(
    MessageBoxFlags::DialogButton button) const {
  if (button == MessageBoxFlags::DIALOGBUTTON_OK &&
      check_button_status_ != CHECKBUTTON_ENABLED) {
    return false;
  }

  return true;
}

bool AboutChromeView::IsDialogButtonVisible(
    MessageBoxFlags::DialogButton button) const {
  if (button == MessageBoxFlags::DIALOGBUTTON_OK &&
      check_button_status_ == CHECKBUTTON_HIDDEN) {
    return false;
  }

  return true;
}

bool AboutChromeView::CanResize() const {
  return false;
}

bool AboutChromeView::CanMaximize() const {
  return false;
}

bool AboutChromeView::IsAlwaysOnTop() const {
  return false;
}

bool AboutChromeView::HasAlwaysOnTopMenu() const {
  return false;
}

bool AboutChromeView::IsModal() const {
  return true;
}

std::wstring AboutChromeView::GetWindowTitle() const {
  return l10n_util::GetString(IDS_ABOUT_CHROME_TITLE);
}

bool AboutChromeView::Accept() {
#if defined(OS_WIN)
  UpdateStatus(UPGRADE_STARTED, GOOGLE_UPDATE_NO_ERROR);

  // The Upgrade button isn't available until we have received notification
  // that an update is available, at which point this pointer should have been
  // null-ed out.
  DCHECK(!google_updater_);
  google_updater_ = new GoogleUpdate();
  google_updater_->AddStatusChangeListener(this);
  // CheckForUpdate(true,...) means perform the upgrade if new version found.
  google_updater_->CheckForUpdate(true, window());
#endif

  return false;  // We never allow this button to close the window.
}

views::View* AboutChromeView::GetContentsView() {
  return this;
}

////////////////////////////////////////////////////////////////////////////////
// AboutChromeView, views::LinkController implementation:

void AboutChromeView::LinkActivated(views::Link* source,
                                    int event_flags) {
  GURL url;
  if (source == terms_of_service_url_)
    url = GURL(chrome::kAboutTermsURL);
  else if (source == chromium_url_)
    url = GURL(WideToUTF16Hack(l10n_util::GetString(IDS_CHROMIUM_PROJECT_URL)));
  else if (source == open_source_url_)
    url = GURL(chrome::kAboutCreditsURL);
  else
    NOTREACHED() << "Unknown link source";

  Browser* browser = BrowserList::GetLastActive();
#if defined(OS_CHROMEOS)
  browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK);
#else
  browser->OpenURL(url, GURL(), NEW_WINDOW, PageTransition::LINK);
#endif
}

#if defined(OS_CHROMEOS)
void AboutChromeView::OnOSVersion(
    chromeos::VersionLoader::Handle handle,
    std::string version) {
  os_version_label_->SetText(UTF8ToUTF16(version));
}
#endif

#if defined(OS_WIN)
////////////////////////////////////////////////////////////////////////////////
// AboutChromeView, GoogleUpdateStatusListener implementation:

void AboutChromeView::OnReportResults(GoogleUpdateUpgradeResult result,
                                      GoogleUpdateErrorCode error_code,
                                      const std::wstring& version) {
  // Drop the last reference to the object so that it gets cleaned up here.
  google_updater_ = NULL;

  // Make a note of which version Google Update is reporting is the latest
  // version.
  new_version_available_ = version;
  UpdateStatus(result, error_code);
}

////////////////////////////////////////////////////////////////////////////////
// AboutChromeView, private:

void AboutChromeView::UpdateStatus(GoogleUpdateUpgradeResult result,
                                   GoogleUpdateErrorCode error_code) {
#if !defined(GOOGLE_CHROME_BUILD)
  // For Chromium builds it would show an error message.
  // But it looks weird because in fact there is no error,
  // just the update server is not available for non-official builds.
  return;
#endif
  bool show_success_indicator = false;
  bool show_update_available_indicator = false;
  bool show_timeout_indicator = false;
  bool show_throbber = false;
  bool show_update_label = true;  // Always visible, except at start.

  switch (result) {
    case UPGRADE_STARTED:
      UserMetrics::RecordAction("Upgrade_Started", profile_);
      check_button_status_ = CHECKBUTTON_DISABLED;
      show_throbber = true;
      update_label_.SetText(l10n_util::GetString(IDS_UPGRADE_STARTED));
      break;
    case UPGRADE_CHECK_STARTED:
      UserMetrics::RecordAction("UpgradeCheck_Started", profile_);
      check_button_status_ = CHECKBUTTON_HIDDEN;
      show_throbber = true;
      update_label_.SetText(l10n_util::GetString(IDS_UPGRADE_CHECK_STARTED));
      break;
    case UPGRADE_IS_AVAILABLE:
      UserMetrics::RecordAction("UpgradeCheck_UpgradeIsAvailable", profile_);
      check_button_status_ = CHECKBUTTON_ENABLED;
      update_label_.SetText(
          l10n_util::GetStringF(IDS_UPGRADE_AVAILABLE,
                                l10n_util::GetString(IDS_PRODUCT_NAME)));
      show_update_available_indicator = true;
      break;
    case UPGRADE_ALREADY_UP_TO_DATE: {
      // Google Update reported that Chrome is up-to-date. Now make sure that we
      // are running the latest version and if not, notify the user by falling
      // into the next case of UPGRADE_SUCCESSFUL.
      scoped_ptr<installer::Version> installed_version(
          InstallUtil::GetChromeVersion(false));
      scoped_ptr<installer::Version> running_version(
          installer::Version::GetVersionFromString(current_version_));
      if (!installed_version.get() ||
          !installed_version->IsHigherThan(running_version.get())) {
        UserMetrics::RecordAction("UpgradeCheck_AlreadyUpToDate", profile_);
        check_button_status_ = CHECKBUTTON_HIDDEN;
        std::wstring update_label_text =
            l10n_util::GetStringF(IDS_UPGRADE_ALREADY_UP_TO_DATE,
                                  l10n_util::GetString(IDS_PRODUCT_NAME),
                                  current_version_);
        if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) {
          update_label_text.push_back(
              static_cast<wchar_t>(l10n_util::kLeftToRightMark));
        }
        update_label_.SetText(update_label_text);
        show_success_indicator = true;
        break;
      }
      // No break here as we want to notify user about upgrade if there is one.
    }
    case UPGRADE_SUCCESSFUL: {
      if (result == UPGRADE_ALREADY_UP_TO_DATE)
        UserMetrics::RecordAction("UpgradeCheck_AlreadyUpgraded", profile_);
      else
        UserMetrics::RecordAction("UpgradeCheck_Upgraded", profile_);
      check_button_status_ = CHECKBUTTON_HIDDEN;
      const std::wstring& update_string = new_version_available_.empty()
          ? l10n_util::GetStringF(IDS_UPGRADE_SUCCESSFUL_NOVERSION,
                                  l10n_util::GetString(IDS_PRODUCT_NAME))
          : l10n_util::GetStringF(IDS_UPGRADE_SUCCESSFUL,
                                  l10n_util::GetString(IDS_PRODUCT_NAME),
                                  new_version_available_);
      update_label_.SetText(update_string);
      show_success_indicator = true;
      RestartMessageBox::ShowMessageBox(window()->GetNativeWindow());
      break;
    }
    case UPGRADE_ERROR:
      UserMetrics::RecordAction("UpgradeCheck_Error", profile_);
      check_button_status_ = CHECKBUTTON_HIDDEN;
      update_label_.SetText(l10n_util::GetStringF(IDS_UPGRADE_ERROR,
                                                  IntToWString(error_code)));
      show_timeout_indicator = true;
      break;
    default:
      NOTREACHED();
  }

  success_indicator_.SetVisible(show_success_indicator);
  update_available_indicator_.SetVisible(show_update_available_indicator);
  timeout_indicator_.SetVisible(show_timeout_indicator);
  update_label_.SetVisible(show_update_label);
  throbber_->SetVisible(show_throbber);
  if (show_throbber)
    throbber_->Start();
  else
    throbber_->Stop();

  // We have updated controls on the parent, so we need to update its layout.
  View* parent = GetParent();
  parent->Layout();

  // Check button may have appeared/disappeared. We cannot call this during
  // ViewHierarchyChanged because the |window()| pointer hasn't been set yet.
  if (window())
    GetDialogClientView()->UpdateDialogButtons();
}

#endif