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