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