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