普通文本  |  569行  |  20.62 KB

// Copyright (c) 2011 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/chromeos/login/eula_view.h"

#include <signal.h>
#include <sys/types.h>
#include <string>

#include "base/basictypes.h"
#include "base/message_loop.h"
#include "base/task.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/cryptohome_library.h"
#include "chrome/browser/chromeos/customization_document.h"
#include "chrome/browser/chromeos/login/background_view.h"
#include "chrome/browser/chromeos/login/help_app_launcher.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/login_utils.h"
#include "chrome/browser/chromeos/login/network_screen_delegate.h"
#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/views/dom_view.h"
#include "chrome/browser/ui/views/window.h"
#include "chrome/common/url_constants.h"
#include "content/browser/site_instance.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/native_web_keyboard_event.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "views/controls/button/checkbox.h"
#include "views/controls/button/native_button_gtk.h"
#include "views/controls/label.h"
#include "views/controls/throbber.h"
#include "views/events/event.h"
#include "views/layout/grid_layout.h"
#include "views/layout/layout_constants.h"
#include "views/layout/layout_manager.h"
#include "views/widget/widget_gtk.h"
#include "views/window/dialog_delegate.h"
#include "views/window/window.h"

using views::WidgetGtk;

namespace {

const int kBorderSize = 10;
const int kCheckboxWidth = 26;
const int kLastButtonHorizontalMargin = 10;
const int kMargin = 20;
const int kTextMargin = 10;
const int kTpmCheckIntervalMs = 500;

// TODO(glotov): this URL should be changed to actual Google ChromeOS EULA.
// See crbug.com/4647
const char kGoogleEulaUrl[] = "about:terms";

enum kLayoutColumnsets {
  SINGLE_CONTROL_ROW,
  SINGLE_CONTROL_WITH_SHIFT_ROW,
  SINGLE_LINK_WITH_SHIFT_ROW,
  LAST_ROW
};

// Helper class that disables using native label for subclassed GTK control.
class EULANativeCheckboxGtk : public views::NativeCheckboxGtk {
 public:
  explicit EULANativeCheckboxGtk(views::Checkbox* checkbox)
      : views::NativeCheckboxGtk(checkbox) {
    set_fast_resize(true);
  }
  virtual ~EULANativeCheckboxGtk() { }
  virtual bool UsesNativeLabel() const { return false; }
  virtual void UpdateLabel() { }
};

// views::Checkbox specialization that uses its internal views::Label
// instead of native one. We need this because native label does not
// support multiline property and we need it for certain languages.
class EULACheckbox : public views::Checkbox {
 public:
  EULACheckbox() { }
  virtual ~EULACheckbox() { }

 protected:
  virtual views::NativeButtonWrapper* CreateWrapper() {
    views::NativeButtonWrapper* native_wrapper =
        new EULANativeCheckboxGtk(this);
    native_wrapper->UpdateChecked();
    return native_wrapper;
  }
};

// A simple LayoutManager that causes the associated view's one child to be
// sized to match the bounds of its parent except the bounds, if set.
struct FillLayoutWithBorder : public views::LayoutManager {
  // Overridden from LayoutManager:
  virtual void Layout(views::View* host) {
    DCHECK(host->has_children());
    host->GetChildViewAt(0)->SetBoundsRect(host->GetContentsBounds());
  }
  virtual gfx::Size GetPreferredSize(views::View* host) {
    return gfx::Size(host->width(), host->height());
  }
};

// System security setting dialog.
class TpmInfoView : public views::View,
                    public views::DialogDelegate {
 public:
  explicit TpmInfoView(std::string* password)
      : ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)),
        password_(password) {
    DCHECK(password_);
  }

  void Init();

 protected:
  // views::DialogDelegate overrides:
  virtual bool Accept() { return true; }
  virtual bool IsModal() const { return true; }
  virtual views::View* GetContentsView() { return this; }
  virtual int GetDialogButtons() const {
    return MessageBoxFlags::DIALOGBUTTON_OK;
  }

  // views::View overrides:
  virtual std::wstring GetWindowTitle() const {
    return UTF16ToWide(
        l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING));
  }

  gfx::Size GetPreferredSize() {
    return gfx::Size(views::Window::GetLocalizedContentsSize(
        IDS_TPM_INFO_DIALOG_WIDTH_CHARS,
        IDS_TPM_INFO_DIALOG_HEIGHT_LINES));
  }

 private:
  void PullPassword();

  ScopedRunnableMethodFactory<TpmInfoView> runnable_method_factory_;

  // Holds pointer to the password storage.
  std::string* password_;

  views::Label* busy_label_;
  views::Label* password_label_;
  views::Throbber* throbber_;

  DISALLOW_COPY_AND_ASSIGN(TpmInfoView);
};

void TpmInfoView::Init() {
  views::GridLayout* layout = views::GridLayout::CreatePanel(this);
  SetLayoutManager(layout);
  views::ColumnSet* column_set = layout->AddColumnSet(0);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0, 0);
  layout->StartRow(0, 0);
  views::Label* label = new views::Label(UTF16ToWide(
      l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING_DESCRIPTION)));
  label->SetMultiLine(true);
  label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  layout->AddView(label);
  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  layout->StartRow(0, 0);
  label = new views::Label(UTF16ToWide(l10n_util::GetStringUTF16(
      IDS_EULA_SYSTEM_SECURITY_SETTING_DESCRIPTION_KEY)));
  label->SetMultiLine(true);
  label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  layout->AddView(label);
  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  column_set = layout->AddColumnSet(1);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0, 0);
  layout->StartRow(0, 1);
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
  gfx::Font password_font =
      rb.GetFont(ResourceBundle::MediumFont).DeriveFont(0, gfx::Font::BOLD);
  // Password will be set later.
  password_label_ = new views::Label(L"", password_font);
  password_label_->SetVisible(false);
  layout->AddView(password_label_);
  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  column_set = layout->AddColumnSet(2);
  column_set->AddPaddingColumn(1, 0);
  // Resize of the throbber and label is not allowed, since we want they to be
  // placed in the center.
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(1, 0);
  // Border padding columns should have the same width. It guaranties that
  // throbber and label will be placed in the center.
  column_set->LinkColumnSizes(0, 4, -1);

  layout->StartRow(0, 2);
  throbber_ = chromeos::CreateDefaultThrobber();
  throbber_->Start();
  layout->AddView(throbber_);
  busy_label_ = new views::Label(
      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_TPM_BUSY)));
  layout->AddView(busy_label_);
  layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing);

  PullPassword();
}

void TpmInfoView::PullPassword() {
  // Since this method is also called directly.
  runnable_method_factory_.RevokeAll();

  chromeos::CryptohomeLibrary* cryptohome =
      chromeos::CrosLibrary::Get()->GetCryptohomeLibrary();

  bool password_acquired = false;
  if (password_->empty() && cryptohome->TpmIsReady()) {
    password_acquired = cryptohome->TpmGetPassword(password_);
    if (!password_acquired) {
      password_->clear();
    } else if (password_->empty()) {
      // For a fresh OOBE flow TPM is uninitialized,
      // ownership process is started at the EULA screen,
      // password is cleared after EULA is accepted.
      LOG(ERROR) << "TPM returned an empty password.";
    }
  }
  if (password_->empty() && !password_acquired) {
    // Password hasn't been acquired, reschedule pulling.
    MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        runnable_method_factory_.NewRunnableMethod(&TpmInfoView::PullPassword),
        kTpmCheckIntervalMs);
  } else {
    password_label_->SetText(ASCIIToWide(*password_));
    password_label_->SetVisible(true);
    busy_label_->SetVisible(false);
    throbber_->Stop();
    throbber_->SetVisible(false);
  }
}

}  // namespace

namespace chromeos {

////////////////////////////////////////////////////////////////////////////////
// EulaView, public:

EulaView::EulaView(chromeos::ScreenObserver* observer)
    : google_eula_label_(NULL),
      google_eula_view_(NULL),
      usage_statistics_checkbox_(NULL),
      learn_more_link_(NULL),
      oem_eula_label_(NULL),
      oem_eula_view_(NULL),
      system_security_settings_link_(NULL),
      back_button_(NULL),
      continue_button_(NULL),
      observer_(observer),
      bubble_(NULL) {
}

EulaView::~EulaView() {
  // bubble_ will be set to NULL in callback.
  if (bubble_)
    bubble_->Close();
}

// Convenience function to set layout's columnsets for this screen.
static void SetUpGridLayout(views::GridLayout* layout) {
  static const int kPadding = kBorderSize + kMargin;
  views::ColumnSet* column_set = layout->AddColumnSet(SINGLE_CONTROL_ROW);
  column_set->AddPaddingColumn(0, kPadding);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(0, kPadding);

  column_set = layout->AddColumnSet(SINGLE_CONTROL_WITH_SHIFT_ROW);
  column_set->AddPaddingColumn(0, kPadding + kTextMargin);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(0, kPadding);

  column_set = layout->AddColumnSet(SINGLE_LINK_WITH_SHIFT_ROW);
  column_set->AddPaddingColumn(0, kPadding + kTextMargin + kCheckboxWidth);
  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(0, kPadding);

  column_set = layout->AddColumnSet(LAST_ROW);
  column_set->AddPaddingColumn(0, kPadding + kTextMargin);
  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(0, kLastButtonHorizontalMargin + kBorderSize);
}

// Convenience function. Returns URL of the OEM EULA page that should be
// displayed using current locale and manifest. Returns empty URL otherwise.
static GURL GetOemEulaPagePath() {
  const StartupCustomizationDocument* customization =
      StartupCustomizationDocument::GetInstance();
  if (customization->IsReady()) {
    std::string locale = customization->initial_locale();
    std::string eula_page = customization->GetEULAPage(locale);
    if (!eula_page.empty())
      return GURL(eula_page);

    VLOG(1) << "No eula found for locale: " << locale;
  } else {
    LOG(ERROR) << "No manifest found.";
  }
  return GURL();
}

void EulaView::Init() {
  // First, command to own the TPM.
  if (chromeos::CrosLibrary::Get()->EnsureLoaded()) {
    chromeos::CrosLibrary::Get()->
        GetCryptohomeLibrary()->TpmCanAttemptOwnership();
  } else {
    LOG(ERROR) << "Cros library not loaded. "
               << "We must have disabled the link that led here.";
  }

  // Use rounded rect background.
  views::Painter* painter = CreateWizardPainter(
      &BorderDefinition::kScreenBorder);
  set_background(
      views::Background::CreateBackgroundPainter(true, painter));

  // Layout created controls.
  views::GridLayout* layout = new views::GridLayout(this);
  SetLayoutManager(layout);
  SetUpGridLayout(layout);

  static const int kPadding = kBorderSize + kMargin;
  layout->AddPaddingRow(0, kPadding);
  layout->StartRow(0, SINGLE_CONTROL_ROW);
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
  gfx::Font label_font =
      rb.GetFont(ResourceBundle::MediumFont).DeriveFont(0, gfx::Font::NORMAL);
  google_eula_label_ = new views::Label(std::wstring(), label_font);
  layout->AddView(google_eula_label_, 1, 1,
                  views::GridLayout::LEADING, views::GridLayout::FILL);

  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
  layout->StartRow(1, SINGLE_CONTROL_ROW);
  views::View* box_view = new views::View();
  box_view->set_border(views::Border::CreateSolidBorder(1, SK_ColorBLACK));
  box_view->SetLayoutManager(new FillLayoutWithBorder());
  layout->AddView(box_view);

  google_eula_view_ = new DOMView();
  box_view->AddChildView(google_eula_view_);

  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
  layout->StartRow(0, SINGLE_CONTROL_WITH_SHIFT_ROW);
  usage_statistics_checkbox_ = new EULACheckbox();
  usage_statistics_checkbox_->SetMultiLine(true);
  usage_statistics_checkbox_->SetChecked(
      observer_->usage_statistics_reporting());
  layout->AddView(usage_statistics_checkbox_);

  layout->StartRow(0, SINGLE_LINK_WITH_SHIFT_ROW);
  learn_more_link_ = new views::Link();
  learn_more_link_->SetController(this);
  layout->AddView(learn_more_link_);

  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
  layout->StartRow(0, SINGLE_CONTROL_ROW);
  oem_eula_label_ = new views::Label(std::wstring(), label_font);
  layout->AddView(oem_eula_label_, 1, 1,
                  views::GridLayout::LEADING, views::GridLayout::FILL);

  oem_eula_page_ = GetOemEulaPagePath();
  if (!oem_eula_page_.is_empty()) {
    layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
    layout->StartRow(1, SINGLE_CONTROL_ROW);
    box_view = new views::View();
    box_view->SetLayoutManager(new FillLayoutWithBorder());
    box_view->set_border(views::Border::CreateSolidBorder(1, SK_ColorBLACK));
    layout->AddView(box_view);

    oem_eula_view_ = new DOMView();
    box_view->AddChildView(oem_eula_view_);
  }

  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
  layout->StartRow(0, LAST_ROW);
  system_security_settings_link_ = new views::Link();
  system_security_settings_link_->SetController(this);

  if (!chromeos::CrosLibrary::Get()->EnsureLoaded() ||
      !chromeos::CrosLibrary::Get()->GetCryptohomeLibrary()->
          TpmIsEnabled()) {
    system_security_settings_link_->SetEnabled(false);
  }

  layout->AddView(system_security_settings_link_);

  back_button_ = new login::WideButton(this, std::wstring());
  layout->AddView(back_button_);

  continue_button_ = new login::WideButton(this, std::wstring());
  layout->AddView(continue_button_);
  layout->AddPaddingRow(0, kPadding);

  UpdateLocalizedStrings();
}

void EulaView::UpdateLocalizedStrings() {
  // Load Google EULA and its title.
  LoadEulaView(google_eula_view_, google_eula_label_, GURL(kGoogleEulaUrl));

  // Load OEM EULA and its title.
  if (!oem_eula_page_.is_empty())
    LoadEulaView(oem_eula_view_, oem_eula_label_, oem_eula_page_);

  // Set tooltip for usage statistics checkbox if the metric is unmanaged.
  if (!usage_statistics_checkbox_->IsEnabled()) {
    usage_statistics_checkbox_->SetTooltipText(
        UTF16ToWide(l10n_util::GetStringUTF16(IDS_OPTION_DISABLED_BY_POLICY)));
  }

  // Set tooltip for system security settings link if TPM is disabled.
  if (!system_security_settings_link_->IsEnabled()) {
    system_security_settings_link_->SetTooltipText(
        UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_TPM_DISABLED)));
  }

  // Load other labels from resources.
  usage_statistics_checkbox_->SetLabel(
      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_CHECKBOX_ENABLE_LOGGING)));
  learn_more_link_->SetText(
      UTF16ToWide(l10n_util::GetStringUTF16(IDS_LEARN_MORE)));
  system_security_settings_link_->SetText(
      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING)));
  continue_button_->SetLabel(UTF16ToWide(
      l10n_util::GetStringUTF16(IDS_EULA_ACCEPT_AND_CONTINUE_BUTTON)));
  back_button_->SetLabel(
      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_BACK_BUTTON)));
}

////////////////////////////////////////////////////////////////////////////////
// EulaView, protected, views::View implementation:

void EulaView::OnLocaleChanged() {
  UpdateLocalizedStrings();
  Layout();
}

////////////////////////////////////////////////////////////////////////////////
// views::ButtonListener implementation:

void EulaView::ButtonPressed(views::Button* sender, const views::Event& event) {
  if (usage_statistics_checkbox_) {
    observer_->set_usage_statistics_reporting(
        usage_statistics_checkbox_->checked());
  }
  if (sender == continue_button_) {
    observer_->OnExit(ScreenObserver::EULA_ACCEPTED);
  } else if (sender == back_button_) {
    observer_->OnExit(ScreenObserver::EULA_BACK);
  }
}

////////////////////////////////////////////////////////////////////////////////
// views::LinkController implementation:

void EulaView::LinkActivated(views::Link* source, int event_flags) {
  gfx::NativeWindow parent_window =
      LoginUtils::Get()->GetBackgroundView()->GetNativeWindow();
  if (source == learn_more_link_) {
    if (!help_app_.get())
      help_app_ = new HelpAppLauncher(parent_window);
    help_app_->ShowHelpTopic(HelpAppLauncher::HELP_STATS_USAGE);
  } else if (source == system_security_settings_link_) {
    TpmInfoView* view = new TpmInfoView(&tpm_password_);
    view->Init();
    views::Window* window = browser::CreateViewsWindow(parent_window,
                                                       gfx::Rect(),
                                                       view);
    window->SetIsAlwaysOnTop(true);
    window->Show();
  }
}

////////////////////////////////////////////////////////////////////////////////
// TabContentsDelegate implementation:

// Convenience function. Queries |eula_view| for HTML title and, if it
// is ready, assigns it to |eula_label| and returns true so the caller
// view calls Layout().
static bool PublishTitleIfReady(const TabContents* contents,
                                DOMView* eula_view,
                                views::Label* eula_label) {
  if (contents != eula_view->tab_contents())
    return false;
  eula_label->SetText(UTF16ToWide(eula_view->tab_contents()->GetTitle()));
  return true;
}

void EulaView::NavigationStateChanged(const TabContents* contents,
                                      unsigned changed_flags) {
  if (changed_flags & TabContents::INVALIDATE_TITLE) {
    if (PublishTitleIfReady(contents, google_eula_view_, google_eula_label_) ||
        PublishTitleIfReady(contents, oem_eula_view_, oem_eula_label_)) {
      Layout();
    }
  }
}

void EulaView::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {
  views::Widget* widget = GetWidget();
  if (widget && event.os_event && !event.skip_in_browser) {
    views::KeyEvent views_event(reinterpret_cast<GdkEvent*>(event.os_event));
    static_cast<views::WidgetGtk*>(widget)->HandleKeyboardEvent(views_event);
  }
}

////////////////////////////////////////////////////////////////////////////////
// EulaView, private:

void EulaView::LoadEulaView(DOMView* eula_view,
                            views::Label* eula_label,
                            const GURL& eula_url) {
  Profile* profile = ProfileManager::GetDefaultProfile();
  eula_view->Init(profile,
                  SiteInstance::CreateSiteInstanceForURL(profile, eula_url));
  eula_view->LoadURL(eula_url);
  eula_view->tab_contents()->set_delegate(this);
}

////////////////////////////////////////////////////////////////////////////////
// EulaView, private, views::View implementation:

bool EulaView::OnKeyPressed(const views::KeyEvent&) {
  // Close message bubble if shown. bubble_ will be set to NULL in callback.
  if (bubble_) {
    bubble_->Close();
    return true;
  }
  return false;
}

}  // namespace chromeos