eula_view.cc revision dc0f95d653279beabeb9817299e2902918ba123e
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/chromeos/login/eula_view.h"
6
7#include <signal.h>
8#include <sys/types.h>
9#include <string>
10
11#include "base/basictypes.h"
12#include "base/message_loop.h"
13#include "base/task.h"
14#include "base/utf_string_conversions.h"
15#include "base/values.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/chromeos/cros/cros_library.h"
18#include "chrome/browser/chromeos/cros/cryptohome_library.h"
19#include "chrome/browser/chromeos/customization_document.h"
20#include "chrome/browser/chromeos/login/background_view.h"
21#include "chrome/browser/chromeos/login/help_app_launcher.h"
22#include "chrome/browser/chromeos/login/helper.h"
23#include "chrome/browser/chromeos/login/login_utils.h"
24#include "chrome/browser/chromeos/login/network_screen_delegate.h"
25#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
26#include "chrome/browser/chromeos/login/wizard_controller.h"
27#include "chrome/browser/chromeos/metrics_cros_settings_provider.h"
28#include "chrome/browser/profiles/profile_manager.h"
29#include "chrome/browser/ui/views/dom_view.h"
30#include "chrome/browser/ui/views/window.h"
31#include "chrome/common/native_web_keyboard_event.h"
32#include "chrome/common/url_constants.h"
33#include "content/browser/site_instance.h"
34#include "content/browser/tab_contents/tab_contents.h"
35#include "grit/chromium_strings.h"
36#include "grit/generated_resources.h"
37#include "grit/locale_settings.h"
38#include "grit/theme_resources.h"
39#include "ui/base/l10n/l10n_util.h"
40#include "ui/base/resource/resource_bundle.h"
41#include "views/controls/button/checkbox.h"
42#include "views/controls/label.h"
43#include "views/controls/throbber.h"
44#include "views/layout/grid_layout.h"
45#include "views/layout/layout_constants.h"
46#include "views/layout/layout_manager.h"
47#include "views/widget/widget_gtk.h"
48#include "views/window/dialog_delegate.h"
49#include "views/window/window.h"
50
51using views::WidgetGtk;
52
53namespace {
54
55const int kBorderSize = 10;
56const int kCheckboxWidth = 20;
57const int kLastButtonHorizontalMargin = 10;
58const int kMargin = 20;
59const int kTextMargin = 10;
60const int kTpmCheckIntervalMs = 500;
61
62// TODO(glotov): this URL should be changed to actual Google ChromeOS EULA.
63// See crbug.com/4647
64const char kGoogleEulaUrl[] = "about:terms";
65
66enum kLayoutColumnsets {
67  SINGLE_CONTROL_ROW,
68  SINGLE_CONTROL_WITH_SHIFT_ROW,
69  SINGLE_LINK_WITH_SHIFT_ROW,
70  LAST_ROW
71};
72
73// A simple LayoutManager that causes the associated view's one child to be
74// sized to match the bounds of its parent except the bounds, if set.
75struct FillLayoutWithBorder : public views::LayoutManager {
76  // Overridden from LayoutManager:
77  virtual void Layout(views::View* host) {
78    DCHECK(host->has_children());
79    host->GetChildViewAt(0)->SetBoundsRect(host->GetContentsBounds());
80  }
81  virtual gfx::Size GetPreferredSize(views::View* host) {
82    return gfx::Size(host->width(), host->height());
83  }
84};
85
86// System security setting dialog.
87class TpmInfoView : public views::View,
88                    public views::DialogDelegate {
89 public:
90  explicit TpmInfoView(std::string* password)
91      : ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)),
92        password_(password) {
93    DCHECK(password_);
94  }
95
96  void Init();
97
98 protected:
99  // views::DialogDelegate overrides:
100  virtual bool Accept() { return true; }
101  virtual bool IsModal() const { return true; }
102  virtual views::View* GetContentsView() { return this; }
103  virtual int GetDialogButtons() const {
104    return MessageBoxFlags::DIALOGBUTTON_OK;
105  }
106
107  // views::View overrides:
108  virtual std::wstring GetWindowTitle() const {
109    return UTF16ToWide(
110        l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING));
111  }
112
113  gfx::Size GetPreferredSize() {
114    return gfx::Size(views::Window::GetLocalizedContentsSize(
115        IDS_TPM_INFO_DIALOG_WIDTH_CHARS,
116        IDS_TPM_INFO_DIALOG_HEIGHT_LINES));
117  }
118
119 private:
120  void PullPassword();
121
122  ScopedRunnableMethodFactory<TpmInfoView> runnable_method_factory_;
123
124  // Holds pointer to the password storage.
125  std::string* password_;
126
127  views::Label* busy_label_;
128  views::Label* password_label_;
129  views::Throbber* throbber_;
130
131  DISALLOW_COPY_AND_ASSIGN(TpmInfoView);
132};
133
134void TpmInfoView::Init() {
135  views::GridLayout* layout = views::GridLayout::CreatePanel(this);
136  SetLayoutManager(layout);
137  views::ColumnSet* column_set = layout->AddColumnSet(0);
138  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
139                        views::GridLayout::USE_PREF, 0, 0);
140  layout->StartRow(0, 0);
141  views::Label* label = new views::Label(UTF16ToWide(
142      l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING_DESCRIPTION)));
143  label->SetMultiLine(true);
144  label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
145  layout->AddView(label);
146  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
147
148  layout->StartRow(0, 0);
149  label = new views::Label(UTF16ToWide(l10n_util::GetStringUTF16(
150      IDS_EULA_SYSTEM_SECURITY_SETTING_DESCRIPTION_KEY)));
151  label->SetMultiLine(true);
152  label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
153  layout->AddView(label);
154  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
155
156  column_set = layout->AddColumnSet(1);
157  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
158                        views::GridLayout::USE_PREF, 0, 0);
159  layout->StartRow(0, 1);
160  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
161  gfx::Font password_font =
162      rb.GetFont(ResourceBundle::MediumFont).DeriveFont(0, gfx::Font::BOLD);
163  // Password will be set later.
164  password_label_ = new views::Label(L"", password_font);
165  password_label_->SetVisible(false);
166  layout->AddView(password_label_);
167  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
168
169  column_set = layout->AddColumnSet(2);
170  column_set->AddPaddingColumn(1, 0);
171  // Resize of the throbber and label is not allowed, since we want they to be
172  // placed in the center.
173  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
174                        views::GridLayout::USE_PREF, 0, 0);
175  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
176  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
177                        views::GridLayout::USE_PREF, 0, 0);
178  column_set->AddPaddingColumn(1, 0);
179  // Border padding columns should have the same width. It guaranties that
180  // throbber and label will be placed in the center.
181  column_set->LinkColumnSizes(0, 4, -1);
182
183  layout->StartRow(0, 2);
184  throbber_ = chromeos::CreateDefaultThrobber();
185  throbber_->Start();
186  layout->AddView(throbber_);
187  busy_label_ = new views::Label(
188      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_TPM_BUSY)));
189  layout->AddView(busy_label_);
190  layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing);
191
192  PullPassword();
193}
194
195void TpmInfoView::PullPassword() {
196  // Since this method is also called directly.
197  runnable_method_factory_.RevokeAll();
198
199  chromeos::CryptohomeLibrary* cryptohome =
200      chromeos::CrosLibrary::Get()->GetCryptohomeLibrary();
201
202  bool password_acquired = false;
203  if (password_->empty() && cryptohome->TpmIsReady()) {
204    password_acquired = cryptohome->TpmGetPassword(password_);
205    if (!password_acquired) {
206      password_->clear();
207    } else if (password_->empty()) {
208      // For a fresh OOBE flow TPM is uninitialized,
209      // ownership process is started at the EULA screen,
210      // password is cleared after EULA is accepted.
211      LOG(ERROR) << "TPM returned an empty password.";
212    }
213  }
214  if (password_->empty() && !password_acquired) {
215    // Password hasn't been acquired, reschedule pulling.
216    MessageLoop::current()->PostDelayedTask(
217        FROM_HERE,
218        runnable_method_factory_.NewRunnableMethod(&TpmInfoView::PullPassword),
219        kTpmCheckIntervalMs);
220  } else {
221    password_label_->SetText(ASCIIToWide(*password_));
222    password_label_->SetVisible(true);
223    busy_label_->SetVisible(false);
224    throbber_->Stop();
225    throbber_->SetVisible(false);
226  }
227}
228
229}  // namespace
230
231namespace chromeos {
232
233////////////////////////////////////////////////////////////////////////////////
234// EulaView, public:
235
236EulaView::EulaView(chromeos::ScreenObserver* observer)
237    : google_eula_label_(NULL),
238      google_eula_view_(NULL),
239      usage_statistics_checkbox_(NULL),
240      learn_more_link_(NULL),
241      oem_eula_label_(NULL),
242      oem_eula_view_(NULL),
243      system_security_settings_link_(NULL),
244      back_button_(NULL),
245      continue_button_(NULL),
246      observer_(observer),
247      bubble_(NULL) {
248}
249
250EulaView::~EulaView() {
251  // bubble_ will be set to NULL in callback.
252  if (bubble_)
253    bubble_->Close();
254}
255
256// Convenience function to set layout's columnsets for this screen.
257static void SetUpGridLayout(views::GridLayout* layout) {
258  static const int kPadding = kBorderSize + kMargin;
259  views::ColumnSet* column_set = layout->AddColumnSet(SINGLE_CONTROL_ROW);
260  column_set->AddPaddingColumn(0, kPadding);
261  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
262                        views::GridLayout::USE_PREF, 0, 0);
263  column_set->AddPaddingColumn(0, kPadding);
264
265  column_set = layout->AddColumnSet(SINGLE_CONTROL_WITH_SHIFT_ROW);
266  column_set->AddPaddingColumn(0, kPadding + kTextMargin);
267  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
268                        views::GridLayout::USE_PREF, 0, 0);
269  column_set->AddPaddingColumn(0, kPadding);
270
271  column_set = layout->AddColumnSet(SINGLE_LINK_WITH_SHIFT_ROW);
272  column_set->AddPaddingColumn(0, kPadding + kTextMargin + kCheckboxWidth);
273  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
274                        views::GridLayout::USE_PREF, 0, 0);
275  column_set->AddPaddingColumn(0, kPadding);
276
277  column_set = layout->AddColumnSet(LAST_ROW);
278  column_set->AddPaddingColumn(0, kPadding + kTextMargin);
279  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
280                        views::GridLayout::USE_PREF, 0, 0);
281  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
282                        views::GridLayout::USE_PREF, 0, 0);
283  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
284  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
285                        views::GridLayout::USE_PREF, 0, 0);
286  column_set->AddPaddingColumn(0, kLastButtonHorizontalMargin + kBorderSize);
287}
288
289// Convenience function. Returns URL of the OEM EULA page that should be
290// displayed using current locale and manifest. Returns empty URL otherwise.
291static GURL GetOemEulaPagePath() {
292  const StartupCustomizationDocument *customization =
293      WizardController::default_controller()->GetCustomization();
294  if (customization) {
295    std::string locale = g_browser_process->GetApplicationLocale();
296    std::string eula_page = customization->GetEULAPage(locale);
297    if (!eula_page.empty())
298      return GURL(eula_page);
299
300    VLOG(1) << "No eula found for locale: " << locale;
301  } else {
302    LOG(ERROR) << "No manifest found.";
303  }
304  return GURL();
305}
306
307void EulaView::Init() {
308  // First, command to own the TPM.
309  if (chromeos::CrosLibrary::Get()->EnsureLoaded()) {
310    chromeos::CrosLibrary::Get()->
311        GetCryptohomeLibrary()->TpmCanAttemptOwnership();
312  } else {
313    LOG(ERROR) << "Cros library not loaded. "
314               << "We must have disabled the link that led here.";
315  }
316
317  // Use rounded rect background.
318  views::Painter* painter = CreateWizardPainter(
319      &BorderDefinition::kScreenBorder);
320  set_background(
321      views::Background::CreateBackgroundPainter(true, painter));
322
323  // Layout created controls.
324  views::GridLayout* layout = new views::GridLayout(this);
325  SetLayoutManager(layout);
326  SetUpGridLayout(layout);
327
328  static const int kPadding = kBorderSize + kMargin;
329  layout->AddPaddingRow(0, kPadding);
330  layout->StartRow(0, SINGLE_CONTROL_ROW);
331  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
332  gfx::Font label_font =
333      rb.GetFont(ResourceBundle::MediumFont).DeriveFont(0, gfx::Font::NORMAL);
334  google_eula_label_ = new views::Label(std::wstring(), label_font);
335  layout->AddView(google_eula_label_, 1, 1,
336                  views::GridLayout::LEADING, views::GridLayout::FILL);
337
338  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
339  layout->StartRow(1, SINGLE_CONTROL_ROW);
340  views::View* box_view = new views::View();
341  box_view->set_border(views::Border::CreateSolidBorder(1, SK_ColorBLACK));
342  box_view->SetLayoutManager(new FillLayoutWithBorder());
343  layout->AddView(box_view);
344
345  google_eula_view_ = new DOMView();
346  box_view->AddChildView(google_eula_view_);
347
348  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
349  layout->StartRow(0, SINGLE_CONTROL_WITH_SHIFT_ROW);
350  usage_statistics_checkbox_ = new views::Checkbox();
351  usage_statistics_checkbox_->SetMultiLine(true);
352  usage_statistics_checkbox_->SetChecked(true);
353  layout->AddView(usage_statistics_checkbox_);
354
355  layout->StartRow(0, SINGLE_LINK_WITH_SHIFT_ROW);
356  learn_more_link_ = new views::Link();
357  learn_more_link_->SetController(this);
358  layout->AddView(learn_more_link_);
359
360  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
361  layout->StartRow(0, SINGLE_CONTROL_ROW);
362  oem_eula_label_ = new views::Label(std::wstring(), label_font);
363  layout->AddView(oem_eula_label_, 1, 1,
364                  views::GridLayout::LEADING, views::GridLayout::FILL);
365
366  oem_eula_page_ = GetOemEulaPagePath();
367  if (!oem_eula_page_.is_empty()) {
368    layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
369    layout->StartRow(1, SINGLE_CONTROL_ROW);
370    box_view = new views::View();
371    box_view->SetLayoutManager(new FillLayoutWithBorder());
372    box_view->set_border(views::Border::CreateSolidBorder(1, SK_ColorBLACK));
373    layout->AddView(box_view);
374
375    oem_eula_view_ = new DOMView();
376    box_view->AddChildView(oem_eula_view_);
377  }
378
379  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
380  layout->StartRow(0, LAST_ROW);
381  system_security_settings_link_ = new views::Link();
382  system_security_settings_link_->SetController(this);
383
384  if (!chromeos::CrosLibrary::Get()->EnsureLoaded() ||
385      !chromeos::CrosLibrary::Get()->GetCryptohomeLibrary()->
386          TpmIsEnabled()) {
387    system_security_settings_link_->SetEnabled(false);
388  }
389
390  layout->AddView(system_security_settings_link_);
391
392  back_button_ = new login::WideButton(this, std::wstring());
393  layout->AddView(back_button_);
394
395  continue_button_ = new login::WideButton(this, std::wstring());
396  layout->AddView(continue_button_);
397  layout->AddPaddingRow(0, kPadding);
398
399  UpdateLocalizedStrings();
400}
401
402void EulaView::UpdateLocalizedStrings() {
403  // Load Google EULA and its title.
404  LoadEulaView(google_eula_view_, google_eula_label_, GURL(kGoogleEulaUrl));
405
406  // Load OEM EULA and its title.
407  if (!oem_eula_page_.is_empty())
408    LoadEulaView(oem_eula_view_, oem_eula_label_, oem_eula_page_);
409
410  // Set tooltip for usage statistics checkbox if the metric is unmanaged.
411  if (!usage_statistics_checkbox_->IsEnabled()) {
412    usage_statistics_checkbox_->SetTooltipText(
413        UTF16ToWide(l10n_util::GetStringUTF16(IDS_OPTION_DISABLED_BY_POLICY)));
414  }
415
416  // Set tooltip for system security settings link if TPM is disabled.
417  if (!system_security_settings_link_->IsEnabled()) {
418    system_security_settings_link_->SetTooltipText(
419        UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_TPM_DISABLED)));
420  }
421
422  // Load other labels from resources.
423  usage_statistics_checkbox_->SetLabel(
424      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_CHECKBOX_ENABLE_LOGGING)));
425  learn_more_link_->SetText(
426      UTF16ToWide(l10n_util::GetStringUTF16(IDS_LEARN_MORE)));
427  system_security_settings_link_->SetText(
428      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING)));
429  continue_button_->SetLabel(UTF16ToWide(
430      l10n_util::GetStringUTF16(IDS_EULA_ACCEPT_AND_CONTINUE_BUTTON)));
431  back_button_->SetLabel(
432      UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_BACK_BUTTON)));
433}
434
435////////////////////////////////////////////////////////////////////////////////
436// EulaView, protected, views::View implementation:
437
438void EulaView::OnLocaleChanged() {
439  UpdateLocalizedStrings();
440  Layout();
441}
442
443////////////////////////////////////////////////////////////////////////////////
444// views::ButtonListener implementation:
445
446void EulaView::ButtonPressed(views::Button* sender, const views::Event& event) {
447  if (sender == continue_button_) {
448    if (usage_statistics_checkbox_) {
449      MetricsCrosSettingsProvider::SetMetricsStatus(
450          usage_statistics_checkbox_->checked());
451    }
452    observer_->OnExit(ScreenObserver::EULA_ACCEPTED);
453  } else if (sender == back_button_) {
454    observer_->OnExit(ScreenObserver::EULA_BACK);
455  }
456}
457
458////////////////////////////////////////////////////////////////////////////////
459// views::LinkController implementation:
460
461void EulaView::LinkActivated(views::Link* source, int event_flags) {
462  gfx::NativeWindow parent_window =
463      LoginUtils::Get()->GetBackgroundView()->GetNativeWindow();
464  if (source == learn_more_link_) {
465    if (!help_app_.get())
466      help_app_ = new HelpAppLauncher(parent_window);
467    help_app_->ShowHelpTopic(HelpAppLauncher::HELP_STATS_USAGE);
468  } else if (source == system_security_settings_link_) {
469    TpmInfoView* view = new TpmInfoView(&tpm_password_);
470    view->Init();
471    views::Window* window = browser::CreateViewsWindow(parent_window,
472                                                       gfx::Rect(),
473                                                       view);
474    window->SetIsAlwaysOnTop(true);
475    window->Show();
476  }
477}
478
479////////////////////////////////////////////////////////////////////////////////
480// TabContentsDelegate implementation:
481
482// Convenience function. Queries |eula_view| for HTML title and, if it
483// is ready, assigns it to |eula_label| and returns true so the caller
484// view calls Layout().
485static bool PublishTitleIfReady(const TabContents* contents,
486                                DOMView* eula_view,
487                                views::Label* eula_label) {
488  if (contents != eula_view->tab_contents())
489    return false;
490  eula_label->SetText(UTF16ToWide(eula_view->tab_contents()->GetTitle()));
491  eula_label->parent()->SetAccessibleName(
492      eula_view->tab_contents()->GetTitle());
493  return true;
494}
495
496void EulaView::NavigationStateChanged(const TabContents* contents,
497                                      unsigned changed_flags) {
498  if (changed_flags & TabContents::INVALIDATE_TITLE) {
499    if (PublishTitleIfReady(contents, google_eula_view_, google_eula_label_) ||
500        PublishTitleIfReady(contents, oem_eula_view_, oem_eula_label_)) {
501      Layout();
502    }
503  }
504}
505
506void EulaView::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {
507  views::Widget* widget = GetWidget();
508  if (widget && event.os_event && !event.skip_in_browser)
509    static_cast<views::WidgetGtk*>(widget)->HandleKeyboardEvent(event.os_event);
510}
511
512////////////////////////////////////////////////////////////////////////////////
513// EulaView, private:
514
515void EulaView::LoadEulaView(DOMView* eula_view,
516                            views::Label* eula_label,
517                            const GURL& eula_url) {
518  Profile* profile = ProfileManager::GetDefaultProfile();
519  eula_view->Init(profile,
520                  SiteInstance::CreateSiteInstanceForURL(profile, eula_url));
521  eula_view->LoadURL(eula_url);
522  eula_view->tab_contents()->set_delegate(this);
523}
524
525////////////////////////////////////////////////////////////////////////////////
526// EulaView, private, views::View implementation:
527
528bool EulaView::OnKeyPressed(const views::KeyEvent&) {
529  // Close message bubble if shown. bubble_ will be set to NULL in callback.
530  if (bubble_) {
531    bubble_->Close();
532    return true;
533  }
534  return false;
535}
536
537}  // namespace chromeos
538