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/background_view.h"
6
7#include <string>
8#include <vector>
9
10#include "base/string16.h"
11#include "base/string_util.h"
12#include "base/stringprintf.h"
13#include "base/utf_string_conversions.h"
14#include "chrome/browser/browser_process.h"
15#include "chrome/browser/chromeos/login/helper.h"
16#include "chrome/browser/chromeos/login/login_utils.h"
17#include "chrome/browser/chromeos/login/oobe_progress_bar.h"
18#include "chrome/browser/chromeos/login/proxy_settings_dialog.h"
19#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
20#include "chrome/browser/chromeos/login/shutdown_button.h"
21#include "chrome/browser/chromeos/login/wizard_controller.h"
22#include "chrome/browser/chromeos/status/clock_menu_button.h"
23#include "chrome/browser/chromeos/status/input_method_menu_button.h"
24#include "chrome/browser/chromeos/status/network_menu_button.h"
25#include "chrome/browser/chromeos/status/status_area_view.h"
26#include "chrome/browser/chromeos/wm_ipc.h"
27#include "chrome/browser/policy/browser_policy_connector.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/chrome_version_info.h"
32#include "googleurl/src/gurl.h"
33#include "grit/chromium_strings.h"
34#include "grit/generated_resources.h"
35#include "grit/theme_resources.h"
36#include "third_party/cros/chromeos_wm_ipc_enums.h"
37#include "ui/base/l10n/l10n_util.h"
38#include "ui/base/resource/resource_bundle.h"
39#include "ui/base/x/x11_util.h"
40#include "ui/gfx/gtk_util.h"
41#include "views/controls/button/text_button.h"
42#include "views/controls/label.h"
43#include "views/screen.h"
44#include "views/widget/widget_gtk.h"
45#include "views/window/window.h"
46
47// X Windows headers have "#define Status int". That interferes with
48// NetworkLibrary header which defines enum "Status".
49#include <X11/cursorfont.h>  // NOLINT
50#include <X11/Xcursor/Xcursor.h>  // NOLINT
51
52using views::Widget;
53using views::WidgetGtk;
54
55namespace {
56
57const SkColor kVersionColor = 0xff5c739f;
58const char kPlatformLabel[] = "cros:";
59
60// Returns the corresponding step id for step constant.
61int GetStepId(size_t step) {
62  switch (step) {
63    case chromeos::BackgroundView::SELECT_NETWORK:
64      return IDS_OOBE_SELECT_NETWORK;
65    case chromeos::BackgroundView::EULA:
66      return IDS_OOBE_EULA;
67    case chromeos::BackgroundView::SIGNIN:
68      return IDS_OOBE_SIGNIN;
69    case chromeos::BackgroundView::REGISTRATION:
70      return IDS_OOBE_REGISTRATION;
71    case chromeos::BackgroundView::PICTURE:
72      return IDS_OOBE_PICTURE;
73    default:
74      NOTREACHED();
75      return 0;
76  }
77}
78
79// The same as TextButton but switches cursor to hand cursor when mouse
80// is over the button.
81class TextButtonWithHandCursorOver : public views::TextButton {
82 public:
83  TextButtonWithHandCursorOver(views::ButtonListener* listener,
84                               const std::wstring& text)
85      : views::TextButton(listener, text) {
86  }
87
88  virtual ~TextButtonWithHandCursorOver() {}
89
90  virtual gfx::NativeCursor GetCursorForPoint(
91      ui::EventType event_type,
92      const gfx::Point& p) {
93    if (!IsEnabled()) {
94      return NULL;
95    }
96    return gfx::GetCursor(GDK_HAND2);
97  }
98
99 private:
100  DISALLOW_COPY_AND_ASSIGN(TextButtonWithHandCursorOver);
101};
102
103// This gets rid of the ugly X default cursor.
104static void ResetXCursor() {
105  // TODO(sky): nuke this once new window manager is in place.
106  Display* display = ui::GetXDisplay();
107  Cursor cursor = XCreateFontCursor(display, XC_left_ptr);
108  XID root_window = ui::GetX11RootWindow();
109  XSetWindowAttributes attr;
110  attr.cursor = cursor;
111  XChangeWindowAttributes(display, root_window, CWCursor, &attr);
112}
113
114}  // namespace
115
116namespace chromeos {
117
118///////////////////////////////////////////////////////////////////////////////
119// BackgroundView public:
120
121BackgroundView::BackgroundView()
122    : status_area_(NULL),
123      os_version_label_(NULL),
124      boot_times_label_(NULL),
125      progress_bar_(NULL),
126      shutdown_button_(NULL),
127      did_paint_(false),
128#if defined(OFFICIAL_BUILD)
129      is_official_build_(true),
130#else
131      is_official_build_(false),
132#endif
133      background_area_(NULL) {
134}
135
136void BackgroundView::Init(const GURL& background_url) {
137  views::Painter* painter = CreateBackgroundPainter();
138  set_background(views::Background::CreateBackgroundPainter(true, painter));
139  InitStatusArea();
140  InitInfoLabels();
141  if (!background_url.is_empty()) {
142    Profile* profile = ProfileManager::GetDefaultProfile();
143    background_area_ = new DOMView();
144    AddChildView(background_area_);
145    background_area_->Init(profile, NULL);
146    background_area_->SetVisible(false);
147    background_area_->LoadURL(background_url);
148  }
149}
150
151void BackgroundView::EnableShutdownButton(bool enable) {
152  if (enable) {
153    if (shutdown_button_)
154      return;
155    shutdown_button_ = new ShutdownButton();
156    shutdown_button_->Init();
157    AddChildView(shutdown_button_);
158  } else {
159    if (!shutdown_button_)
160      return;
161    delete shutdown_button_;
162    shutdown_button_ = NULL;
163    SchedulePaint();
164  }
165}
166
167// static
168views::Widget* BackgroundView::CreateWindowContainingView(
169    const gfx::Rect& bounds,
170    const GURL& background_url,
171    BackgroundView** view) {
172  ResetXCursor();
173
174  Widget* window = Widget::CreateWidget(
175      Widget::CreateParams(Widget::CreateParams::TYPE_WINDOW));
176  window->Init(NULL, bounds);
177  *view = new BackgroundView();
178  (*view)->Init(background_url);
179
180  if ((*view)->ScreenSaverEnabled())
181    (*view)->ShowScreenSaver();
182
183  window->SetContentsView(*view);
184
185  (*view)->UpdateWindowType();
186
187  // This keeps the window from flashing at startup.
188  GdkWindow* gdk_window = window->GetNativeView()->window;
189  gdk_window_set_back_pixmap(gdk_window, NULL, false);
190
191  LoginUtils::Get()->SetBackgroundView(*view);
192
193  return window;
194}
195
196void BackgroundView::CreateModalPopup(views::WindowDelegate* view) {
197  views::Window* window = browser::CreateViewsWindow(
198      GetNativeWindow(), gfx::Rect(), view);
199  window->SetIsAlwaysOnTop(true);
200  window->Show();
201}
202
203gfx::NativeWindow BackgroundView::GetNativeWindow() const {
204  return
205      GTK_WINDOW(static_cast<const WidgetGtk*>(GetWidget())->GetNativeView());
206}
207
208void BackgroundView::SetStatusAreaVisible(bool visible) {
209  status_area_->SetVisible(visible);
210}
211
212void BackgroundView::SetStatusAreaEnabled(bool enable) {
213  status_area_->MakeButtonsActive(enable);
214}
215
216void BackgroundView::SetOobeProgressBarVisible(bool visible) {
217  if (!progress_bar_ && visible)
218    InitProgressBar();
219
220  if (progress_bar_)
221    progress_bar_->SetVisible(visible);
222}
223
224bool BackgroundView::IsOobeProgressBarVisible() {
225  return progress_bar_ && progress_bar_->IsVisible();
226}
227
228void BackgroundView::SetOobeProgress(LoginStep step) {
229  DCHECK(step < STEPS_COUNT);
230  if (progress_bar_)
231    progress_bar_->SetStep(GetStepId(step));
232}
233
234void BackgroundView::ShowScreenSaver() {
235  SetStatusAreaVisible(false);
236  background_area_->SetVisible(true);
237}
238
239void BackgroundView::HideScreenSaver() {
240  SetStatusAreaVisible(true);
241  // TODO(oshima): we need a way to suspend screen saver
242  // to save power when it's not visible.
243  background_area_->SetVisible(false);
244}
245
246bool BackgroundView::IsScreenSaverVisible() {
247  return ScreenSaverEnabled() && background_area_->IsVisible();
248}
249
250bool BackgroundView::ScreenSaverEnabled() {
251  return background_area_ != NULL;
252}
253
254///////////////////////////////////////////////////////////////////////////////
255// BackgroundView protected:
256
257void BackgroundView::OnPaint(gfx::Canvas* canvas) {
258  views::View::OnPaint(canvas);
259  if (!did_paint_) {
260    did_paint_ = true;
261    UpdateWindowType();
262  }
263}
264
265void BackgroundView::Layout() {
266  const int kCornerPadding = 5;
267  const int kInfoLeftPadding = 10;
268  const int kInfoBottomPadding = 10;
269  const int kInfoBetweenLinesPadding = 1;
270  const int kProgressBarBottomPadding = 20;
271  const int kProgressBarWidth = 750;
272  const int kProgressBarHeight = 70;
273  gfx::Size status_area_size = status_area_->GetPreferredSize();
274  status_area_->SetBounds(
275      width() - status_area_size.width() - kCornerPadding,
276      kCornerPadding,
277      status_area_size.width(),
278      status_area_size.height());
279  gfx::Size version_size = os_version_label_->GetPreferredSize();
280  int os_version_y = height() - version_size.height() - kInfoBottomPadding;
281  if (!is_official_build_)
282    os_version_y -= (version_size.height() + kInfoBetweenLinesPadding);
283  os_version_label_->SetBounds(
284      kInfoLeftPadding,
285      os_version_y,
286      width() - 2 * kInfoLeftPadding,
287      version_size.height());
288  if (!is_official_build_) {
289    boot_times_label_->SetBounds(
290        kInfoLeftPadding,
291        height() - (version_size.height() + kInfoBottomPadding),
292        width() - 2 * kInfoLeftPadding,
293        version_size.height());
294  }
295  if (progress_bar_) {
296    progress_bar_->SetBounds(
297        (width() - kProgressBarWidth) / 2,
298        (height() - kProgressBarBottomPadding - kProgressBarHeight),
299        kProgressBarWidth,
300        kProgressBarHeight);
301  }
302  if (shutdown_button_) {
303    shutdown_button_->LayoutIn(this);
304  }
305  if (background_area_)
306    background_area_->SetBoundsRect(this->bounds());
307}
308
309void BackgroundView::ChildPreferredSizeChanged(View* child) {
310  Layout();
311  SchedulePaint();
312}
313
314bool BackgroundView::ShouldOpenButtonOptions(
315    const views::View* button_view) const {
316  if (button_view == status_area_->network_view()) {
317    return true;
318  }
319  if (button_view == status_area_->clock_view() ||
320      button_view == status_area_->input_method_view()) {
321    return false;
322  }
323  return true;
324}
325
326void BackgroundView::OpenButtonOptions(const views::View* button_view) {
327  if (button_view == status_area_->network_view()) {
328    if (proxy_settings_dialog_.get() == NULL) {
329      proxy_settings_dialog_.reset(new ProxySettingsDialog(
330          this, GetNativeWindow()));
331    }
332    proxy_settings_dialog_->Show();
333  }
334}
335
336StatusAreaHost::ScreenMode BackgroundView::GetScreenMode() const {
337  return kLoginMode;
338}
339
340StatusAreaHost::TextStyle BackgroundView::GetTextStyle() const {
341  return kWhitePlain;
342}
343
344// Overridden from LoginHtmlDialog::Delegate:
345void BackgroundView::OnLocaleChanged() {
346  // Proxy settings dialog contains localized strings.
347  proxy_settings_dialog_.reset();
348  InitInfoLabels();
349  SchedulePaint();
350}
351
352///////////////////////////////////////////////////////////////////////////////
353// BackgroundView private:
354
355void BackgroundView::InitStatusArea() {
356  DCHECK(status_area_ == NULL);
357  status_area_ = new StatusAreaView(this);
358  status_area_->Init();
359  AddChildView(status_area_);
360}
361
362void BackgroundView::InitInfoLabels() {
363  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
364
365  {
366    int idx = GetIndexOf(os_version_label_);
367    delete os_version_label_;
368    os_version_label_ = new views::Label();
369    os_version_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
370    os_version_label_->SetColor(kVersionColor);
371    os_version_label_->SetFont(rb.GetFont(ResourceBundle::SmallFont));
372    if (idx < 0)
373      AddChildView(os_version_label_);
374    else
375      AddChildViewAt(os_version_label_, idx);
376  }
377  if (!is_official_build_) {
378    int idx = GetIndexOf(boot_times_label_);
379    delete boot_times_label_;
380    boot_times_label_ = new views::Label();
381    boot_times_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
382    boot_times_label_->SetColor(kVersionColor);
383    boot_times_label_->SetFont(rb.GetFont(ResourceBundle::SmallFont));
384    if (idx < 0)
385      AddChildView(boot_times_label_);
386    else
387      AddChildViewAt(boot_times_label_, idx);
388  }
389
390  if (CrosLibrary::Get()->EnsureLoaded()) {
391    version_loader_.EnablePlatformVersions(true);
392    version_loader_.GetVersion(
393        &version_consumer_,
394        NewCallback(this, &BackgroundView::OnVersion),
395        is_official_build_ ?
396            VersionLoader::VERSION_SHORT_WITH_DATE :
397            VersionLoader::VERSION_FULL);
398    if (!is_official_build_) {
399      boot_times_loader_.GetBootTimes(
400          &boot_times_consumer_,
401          NewCallback(this, &BackgroundView::OnBootTimes));
402    }
403  } else {
404    UpdateVersionLabel();
405  }
406
407  policy::CloudPolicySubsystem* cloud_policy =
408      g_browser_process->browser_policy_connector()->cloud_policy_subsystem();
409  if (cloud_policy) {
410    cloud_policy_registrar_.reset(
411        new policy::CloudPolicySubsystem::ObserverRegistrar(
412          cloud_policy, this));
413
414    // Ensure that we have up-to-date enterprise info in case enterprise policy
415    // is already fetched and has finished initialization.
416    UpdateEnterpriseInfo();
417  }
418}
419
420void BackgroundView::InitProgressBar() {
421  std::vector<int> steps;
422  steps.push_back(GetStepId(SELECT_NETWORK));
423#if defined(OFFICIAL_BUILD)
424  steps.push_back(GetStepId(EULA));
425#endif
426  steps.push_back(GetStepId(SIGNIN));
427#if defined(OFFICIAL_BUILD)
428  if (WizardController::IsRegisterScreenDefined())
429    steps.push_back(GetStepId(REGISTRATION));
430#endif
431  steps.push_back(GetStepId(PICTURE));
432  progress_bar_ = new OobeProgressBar(steps);
433  AddChildView(progress_bar_);
434}
435
436void BackgroundView::UpdateWindowType() {
437  std::vector<int> params;
438  params.push_back(did_paint_ ? 1 : 0);
439  WmIpc::instance()->SetWindowType(
440      GTK_WIDGET(GetNativeWindow()),
441      WM_IPC_WINDOW_LOGIN_BACKGROUND,
442      &params);
443}
444
445void BackgroundView::UpdateVersionLabel() {
446  if (!CrosLibrary::Get()->EnsureLoaded()) {
447    os_version_label_->SetText(
448        ASCIIToWide(CrosLibrary::Get()->load_error_string()));
449    return;
450  }
451
452  if (version_text_.empty())
453    return;
454
455  chrome::VersionInfo version_info;
456  std::string label_text = l10n_util::GetStringUTF8(IDS_PRODUCT_NAME);
457  label_text += ' ';
458  label_text += version_info.Version();
459  label_text += " (";
460  // TODO(rkc): Fix this. This needs to be in a resource file, but we have had
461  // to put it in for merge into R12. Also, look at rtl implications for this
462  // entire string composition code.
463  label_text += kPlatformLabel;
464  label_text += ' ';
465  label_text += version_text_;
466  label_text += ')';
467
468  if (!enterprise_domain_text_.empty()) {
469    label_text += ' ';
470    if (enterprise_status_text_.empty()) {
471      label_text += l10n_util::GetStringFUTF8(
472          IDS_LOGIN_MANAGED_BY_LABEL_FORMAT,
473          UTF8ToUTF16(enterprise_domain_text_));
474    } else {
475      label_text += l10n_util::GetStringFUTF8(
476          IDS_LOGIN_MANAGED_BY_WITH_STATUS_LABEL_FORMAT,
477          UTF8ToUTF16(enterprise_domain_text_),
478          UTF8ToUTF16(enterprise_status_text_));
479    }
480  }
481
482  // Workaround over incorrect width calculation in old fonts.
483  // TODO(glotov): remove the following line when new fonts are used.
484  label_text += ' ';
485
486  os_version_label_->SetText(UTF8ToWide(label_text));
487}
488
489void BackgroundView::UpdateEnterpriseInfo() {
490  policy::BrowserPolicyConnector* policy_connector =
491      g_browser_process->browser_policy_connector();
492
493  std::string status_text;
494  policy::CloudPolicySubsystem* cloud_policy_subsystem =
495      policy_connector->cloud_policy_subsystem();
496  if (cloud_policy_subsystem) {
497    switch (cloud_policy_subsystem->state()) {
498      case policy::CloudPolicySubsystem::UNENROLLED:
499        status_text = l10n_util::GetStringUTF8(
500            IDS_LOGIN_MANAGED_BY_STATUS_PENDING);
501        break;
502      case policy::CloudPolicySubsystem::UNMANAGED:
503      case policy::CloudPolicySubsystem::BAD_GAIA_TOKEN:
504      case policy::CloudPolicySubsystem::LOCAL_ERROR:
505        status_text = l10n_util::GetStringUTF8(
506            IDS_LOGIN_MANAGED_BY_STATUS_LOST_CONNECTION);
507        break;
508      case policy::CloudPolicySubsystem::NETWORK_ERROR:
509        status_text = l10n_util::GetStringUTF8(
510            IDS_LOGIN_MANAGED_BY_STATUS_NETWORK_ERROR);
511        break;
512      case policy::CloudPolicySubsystem::TOKEN_FETCHED:
513      case policy::CloudPolicySubsystem::SUCCESS:
514        break;
515    }
516  }
517
518  SetEnterpriseInfo(policy_connector->GetEnterpriseDomain(), status_text);
519}
520
521void BackgroundView::SetEnterpriseInfo(const std::string& domain_name,
522                                       const std::string& status_text) {
523  if (domain_name != enterprise_domain_text_ ||
524      status_text != enterprise_status_text_) {
525    enterprise_domain_text_ = domain_name;
526    enterprise_status_text_ = status_text;
527    UpdateVersionLabel();
528  }
529}
530
531void BackgroundView::OnVersion(
532    VersionLoader::Handle handle, std::string version) {
533  version_text_.swap(version);
534  UpdateVersionLabel();
535}
536
537void BackgroundView::OnBootTimes(
538    BootTimesLoader::Handle handle, BootTimesLoader::BootTimes boot_times) {
539  const char* kBootTimesNoChromeExec =
540      "Non-firmware boot took %.2f seconds (kernel %.2fs, system %.2fs)";
541  const char* kBootTimesChromeExec =
542      "Non-firmware boot took %.2f seconds "
543      "(kernel %.2fs, system %.2fs, chrome %.2fs)";
544  std::string boot_times_text;
545
546  if (boot_times.chrome > 0) {
547    boot_times_text =
548        base::StringPrintf(
549            kBootTimesChromeExec,
550            boot_times.total,
551            boot_times.pre_startup,
552            boot_times.system,
553            boot_times.chrome);
554  } else {
555    boot_times_text =
556        base::StringPrintf(
557            kBootTimesNoChromeExec,
558            boot_times.total,
559            boot_times.pre_startup,
560            boot_times.system);
561  }
562  // Use UTF8ToWide once this string is localized.
563  boot_times_label_->SetText(ASCIIToWide(boot_times_text));
564}
565
566void BackgroundView::OnPolicyStateChanged(
567    policy::CloudPolicySubsystem::PolicySubsystemState state,
568    policy::CloudPolicySubsystem::ErrorDetails error_details) {
569  UpdateEnterpriseInfo();
570}
571
572}  // namespace chromeos
573