// 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