// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/background_view.h"
#include <string>
#include <vector>
#include "base/string16.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/login_utils.h"
#include "chrome/browser/chromeos/login/oobe_progress_bar.h"
#include "chrome/browser/chromeos/login/proxy_settings_dialog.h"
#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
#include "chrome/browser/chromeos/login/shutdown_button.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/chromeos/status/clock_menu_button.h"
#include "chrome/browser/chromeos/status/input_method_menu_button.h"
#include "chrome/browser/chromeos/status/network_menu_button.h"
#include "chrome/browser/chromeos/status/status_area_view.h"
#include "chrome/browser/chromeos/wm_ipc.h"
#include "chrome/browser/policy/browser_policy_connector.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/views/dom_view.h"
#include "chrome/browser/ui/views/window.h"
#include "chrome/common/chrome_version_info.h"
#include "googleurl/src/gurl.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "third_party/cros/chromeos_wm_ipc_enums.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/gtk_util.h"
#include "views/controls/button/text_button.h"
#include "views/controls/label.h"
#include "views/screen.h"
#include "views/widget/widget_gtk.h"
#include "views/window/window.h"
// X Windows headers have "#define Status int". That interferes with
// NetworkLibrary header which defines enum "Status".
#include <X11/cursorfont.h> // NOLINT
#include <X11/Xcursor/Xcursor.h> // NOLINT
using views::Widget;
using views::WidgetGtk;
namespace {
const SkColor kVersionColor = 0xff5c739f;
const char kPlatformLabel[] = "cros:";
// Returns the corresponding step id for step constant.
int GetStepId(size_t step) {
switch (step) {
case chromeos::BackgroundView::SELECT_NETWORK:
return IDS_OOBE_SELECT_NETWORK;
case chromeos::BackgroundView::EULA:
return IDS_OOBE_EULA;
case chromeos::BackgroundView::SIGNIN:
return IDS_OOBE_SIGNIN;
case chromeos::BackgroundView::REGISTRATION:
return IDS_OOBE_REGISTRATION;
case chromeos::BackgroundView::PICTURE:
return IDS_OOBE_PICTURE;
default:
NOTREACHED();
return 0;
}
}
// The same as TextButton but switches cursor to hand cursor when mouse
// is over the button.
class TextButtonWithHandCursorOver : public views::TextButton {
public:
TextButtonWithHandCursorOver(views::ButtonListener* listener,
const std::wstring& text)
: views::TextButton(listener, text) {
}
virtual ~TextButtonWithHandCursorOver() {}
virtual gfx::NativeCursor GetCursorForPoint(
ui::EventType event_type,
const gfx::Point& p) {
if (!IsEnabled()) {
return NULL;
}
return gfx::GetCursor(GDK_HAND2);
}
private:
DISALLOW_COPY_AND_ASSIGN(TextButtonWithHandCursorOver);
};
// This gets rid of the ugly X default cursor.
static void ResetXCursor() {
// TODO(sky): nuke this once new window manager is in place.
Display* display = ui::GetXDisplay();
Cursor cursor = XCreateFontCursor(display, XC_left_ptr);
XID root_window = ui::GetX11RootWindow();
XSetWindowAttributes attr;
attr.cursor = cursor;
XChangeWindowAttributes(display, root_window, CWCursor, &attr);
}
} // namespace
namespace chromeos {
///////////////////////////////////////////////////////////////////////////////
// BackgroundView public:
BackgroundView::BackgroundView()
: status_area_(NULL),
os_version_label_(NULL),
boot_times_label_(NULL),
progress_bar_(NULL),
shutdown_button_(NULL),
did_paint_(false),
#if defined(OFFICIAL_BUILD)
is_official_build_(true),
#else
is_official_build_(false),
#endif
background_area_(NULL) {
}
void BackgroundView::Init(const GURL& background_url) {
views::Painter* painter = CreateBackgroundPainter();
set_background(views::Background::CreateBackgroundPainter(true, painter));
InitStatusArea();
InitInfoLabels();
if (!background_url.is_empty()) {
Profile* profile = ProfileManager::GetDefaultProfile();
background_area_ = new DOMView();
AddChildView(background_area_);
background_area_->Init(profile, NULL);
background_area_->SetVisible(false);
background_area_->LoadURL(background_url);
}
}
void BackgroundView::EnableShutdownButton(bool enable) {
if (enable) {
if (shutdown_button_)
return;
shutdown_button_ = new ShutdownButton();
shutdown_button_->Init();
AddChildView(shutdown_button_);
} else {
if (!shutdown_button_)
return;
delete shutdown_button_;
shutdown_button_ = NULL;
SchedulePaint();
}
}
// static
views::Widget* BackgroundView::CreateWindowContainingView(
const gfx::Rect& bounds,
const GURL& background_url,
BackgroundView** view) {
ResetXCursor();
Widget* window = Widget::CreateWidget(
Widget::CreateParams(Widget::CreateParams::TYPE_WINDOW));
window->Init(NULL, bounds);
*view = new BackgroundView();
(*view)->Init(background_url);
if ((*view)->ScreenSaverEnabled())
(*view)->ShowScreenSaver();
window->SetContentsView(*view);
(*view)->UpdateWindowType();
// This keeps the window from flashing at startup.
GdkWindow* gdk_window = window->GetNativeView()->window;
gdk_window_set_back_pixmap(gdk_window, NULL, false);
LoginUtils::Get()->SetBackgroundView(*view);
return window;
}
void BackgroundView::CreateModalPopup(views::WindowDelegate* view) {
views::Window* window = browser::CreateViewsWindow(
GetNativeWindow(), gfx::Rect(), view);
window->SetIsAlwaysOnTop(true);
window->Show();
}
gfx::NativeWindow BackgroundView::GetNativeWindow() const {
return
GTK_WINDOW(static_cast<const WidgetGtk*>(GetWidget())->GetNativeView());
}
void BackgroundView::SetStatusAreaVisible(bool visible) {
status_area_->SetVisible(visible);
}
void BackgroundView::SetStatusAreaEnabled(bool enable) {
status_area_->MakeButtonsActive(enable);
}
void BackgroundView::SetOobeProgressBarVisible(bool visible) {
if (!progress_bar_ && visible)
InitProgressBar();
if (progress_bar_)
progress_bar_->SetVisible(visible);
}
bool BackgroundView::IsOobeProgressBarVisible() {
return progress_bar_ && progress_bar_->IsVisible();
}
void BackgroundView::SetOobeProgress(LoginStep step) {
DCHECK(step < STEPS_COUNT);
if (progress_bar_)
progress_bar_->SetStep(GetStepId(step));
}
void BackgroundView::ShowScreenSaver() {
SetStatusAreaVisible(false);
background_area_->SetVisible(true);
}
void BackgroundView::HideScreenSaver() {
SetStatusAreaVisible(true);
// TODO(oshima): we need a way to suspend screen saver
// to save power when it's not visible.
background_area_->SetVisible(false);
}
bool BackgroundView::IsScreenSaverVisible() {
return ScreenSaverEnabled() && background_area_->IsVisible();
}
bool BackgroundView::ScreenSaverEnabled() {
return background_area_ != NULL;
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundView protected:
void BackgroundView::OnPaint(gfx::Canvas* canvas) {
views::View::OnPaint(canvas);
if (!did_paint_) {
did_paint_ = true;
UpdateWindowType();
}
}
void BackgroundView::Layout() {
const int kCornerPadding = 5;
const int kInfoLeftPadding = 10;
const int kInfoBottomPadding = 10;
const int kInfoBetweenLinesPadding = 1;
const int kProgressBarBottomPadding = 20;
const int kProgressBarWidth = 750;
const int kProgressBarHeight = 70;
gfx::Size status_area_size = status_area_->GetPreferredSize();
status_area_->SetBounds(
width() - status_area_size.width() - kCornerPadding,
kCornerPadding,
status_area_size.width(),
status_area_size.height());
gfx::Size version_size = os_version_label_->GetPreferredSize();
int os_version_y = height() - version_size.height() - kInfoBottomPadding;
if (!is_official_build_)
os_version_y -= (version_size.height() + kInfoBetweenLinesPadding);
os_version_label_->SetBounds(
kInfoLeftPadding,
os_version_y,
width() - 2 * kInfoLeftPadding,
version_size.height());
if (!is_official_build_) {
boot_times_label_->SetBounds(
kInfoLeftPadding,
height() - (version_size.height() + kInfoBottomPadding),
width() - 2 * kInfoLeftPadding,
version_size.height());
}
if (progress_bar_) {
progress_bar_->SetBounds(
(width() - kProgressBarWidth) / 2,
(height() - kProgressBarBottomPadding - kProgressBarHeight),
kProgressBarWidth,
kProgressBarHeight);
}
if (shutdown_button_) {
shutdown_button_->LayoutIn(this);
}
if (background_area_)
background_area_->SetBoundsRect(this->bounds());
}
void BackgroundView::ChildPreferredSizeChanged(View* child) {
Layout();
SchedulePaint();
}
bool BackgroundView::ShouldOpenButtonOptions(
const views::View* button_view) const {
if (button_view == status_area_->network_view()) {
return true;
}
if (button_view == status_area_->clock_view() ||
button_view == status_area_->input_method_view()) {
return false;
}
return true;
}
void BackgroundView::OpenButtonOptions(const views::View* button_view) {
if (button_view == status_area_->network_view()) {
if (proxy_settings_dialog_.get() == NULL) {
proxy_settings_dialog_.reset(new ProxySettingsDialog(
this, GetNativeWindow()));
}
proxy_settings_dialog_->Show();
}
}
StatusAreaHost::ScreenMode BackgroundView::GetScreenMode() const {
return kLoginMode;
}
StatusAreaHost::TextStyle BackgroundView::GetTextStyle() const {
return kWhitePlain;
}
// Overridden from LoginHtmlDialog::Delegate:
void BackgroundView::OnLocaleChanged() {
// Proxy settings dialog contains localized strings.
proxy_settings_dialog_.reset();
InitInfoLabels();
SchedulePaint();
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundView private:
void BackgroundView::InitStatusArea() {
DCHECK(status_area_ == NULL);
status_area_ = new StatusAreaView(this);
status_area_->Init();
AddChildView(status_area_);
}
void BackgroundView::InitInfoLabels() {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
{
int idx = GetIndexOf(os_version_label_);
delete os_version_label_;
os_version_label_ = new views::Label();
os_version_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
os_version_label_->SetColor(kVersionColor);
os_version_label_->SetFont(rb.GetFont(ResourceBundle::SmallFont));
if (idx < 0)
AddChildView(os_version_label_);
else
AddChildViewAt(os_version_label_, idx);
}
if (!is_official_build_) {
int idx = GetIndexOf(boot_times_label_);
delete boot_times_label_;
boot_times_label_ = new views::Label();
boot_times_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
boot_times_label_->SetColor(kVersionColor);
boot_times_label_->SetFont(rb.GetFont(ResourceBundle::SmallFont));
if (idx < 0)
AddChildView(boot_times_label_);
else
AddChildViewAt(boot_times_label_, idx);
}
if (CrosLibrary::Get()->EnsureLoaded()) {
version_loader_.EnablePlatformVersions(true);
version_loader_.GetVersion(
&version_consumer_,
NewCallback(this, &BackgroundView::OnVersion),
is_official_build_ ?
VersionLoader::VERSION_SHORT_WITH_DATE :
VersionLoader::VERSION_FULL);
if (!is_official_build_) {
boot_times_loader_.GetBootTimes(
&boot_times_consumer_,
NewCallback(this, &BackgroundView::OnBootTimes));
}
} else {
UpdateVersionLabel();
}
policy::CloudPolicySubsystem* cloud_policy =
g_browser_process->browser_policy_connector()->cloud_policy_subsystem();
if (cloud_policy) {
cloud_policy_registrar_.reset(
new policy::CloudPolicySubsystem::ObserverRegistrar(
cloud_policy, this));
// Ensure that we have up-to-date enterprise info in case enterprise policy
// is already fetched and has finished initialization.
UpdateEnterpriseInfo();
}
}
void BackgroundView::InitProgressBar() {
std::vector<int> steps;
steps.push_back(GetStepId(SELECT_NETWORK));
#if defined(OFFICIAL_BUILD)
steps.push_back(GetStepId(EULA));
#endif
steps.push_back(GetStepId(SIGNIN));
#if defined(OFFICIAL_BUILD)
if (WizardController::IsRegisterScreenDefined())
steps.push_back(GetStepId(REGISTRATION));
#endif
steps.push_back(GetStepId(PICTURE));
progress_bar_ = new OobeProgressBar(steps);
AddChildView(progress_bar_);
}
void BackgroundView::UpdateWindowType() {
std::vector<int> params;
params.push_back(did_paint_ ? 1 : 0);
WmIpc::instance()->SetWindowType(
GTK_WIDGET(GetNativeWindow()),
WM_IPC_WINDOW_LOGIN_BACKGROUND,
¶ms);
}
void BackgroundView::UpdateVersionLabel() {
if (!CrosLibrary::Get()->EnsureLoaded()) {
os_version_label_->SetText(
ASCIIToWide(CrosLibrary::Get()->load_error_string()));
return;
}
if (version_text_.empty())
return;
chrome::VersionInfo version_info;
std::string label_text = l10n_util::GetStringUTF8(IDS_PRODUCT_NAME);
label_text += ' ';
label_text += version_info.Version();
label_text += " (";
// TODO(rkc): Fix this. This needs to be in a resource file, but we have had
// to put it in for merge into R12. Also, look at rtl implications for this
// entire string composition code.
label_text += kPlatformLabel;
label_text += ' ';
label_text += version_text_;
label_text += ')';
if (!enterprise_domain_text_.empty()) {
label_text += ' ';
if (enterprise_status_text_.empty()) {
label_text += l10n_util::GetStringFUTF8(
IDS_LOGIN_MANAGED_BY_LABEL_FORMAT,
UTF8ToUTF16(enterprise_domain_text_));
} else {
label_text += l10n_util::GetStringFUTF8(
IDS_LOGIN_MANAGED_BY_WITH_STATUS_LABEL_FORMAT,
UTF8ToUTF16(enterprise_domain_text_),
UTF8ToUTF16(enterprise_status_text_));
}
}
// Workaround over incorrect width calculation in old fonts.
// TODO(glotov): remove the following line when new fonts are used.
label_text += ' ';
os_version_label_->SetText(UTF8ToWide(label_text));
}
void BackgroundView::UpdateEnterpriseInfo() {
policy::BrowserPolicyConnector* policy_connector =
g_browser_process->browser_policy_connector();
std::string status_text;
policy::CloudPolicySubsystem* cloud_policy_subsystem =
policy_connector->cloud_policy_subsystem();
if (cloud_policy_subsystem) {
switch (cloud_policy_subsystem->state()) {
case policy::CloudPolicySubsystem::UNENROLLED:
status_text = l10n_util::GetStringUTF8(
IDS_LOGIN_MANAGED_BY_STATUS_PENDING);
break;
case policy::CloudPolicySubsystem::UNMANAGED:
case policy::CloudPolicySubsystem::BAD_GAIA_TOKEN:
case policy::CloudPolicySubsystem::LOCAL_ERROR:
status_text = l10n_util::GetStringUTF8(
IDS_LOGIN_MANAGED_BY_STATUS_LOST_CONNECTION);
break;
case policy::CloudPolicySubsystem::NETWORK_ERROR:
status_text = l10n_util::GetStringUTF8(
IDS_LOGIN_MANAGED_BY_STATUS_NETWORK_ERROR);
break;
case policy::CloudPolicySubsystem::TOKEN_FETCHED:
case policy::CloudPolicySubsystem::SUCCESS:
break;
}
}
SetEnterpriseInfo(policy_connector->GetEnterpriseDomain(), status_text);
}
void BackgroundView::SetEnterpriseInfo(const std::string& domain_name,
const std::string& status_text) {
if (domain_name != enterprise_domain_text_ ||
status_text != enterprise_status_text_) {
enterprise_domain_text_ = domain_name;
enterprise_status_text_ = status_text;
UpdateVersionLabel();
}
}
void BackgroundView::OnVersion(
VersionLoader::Handle handle, std::string version) {
version_text_.swap(version);
UpdateVersionLabel();
}
void BackgroundView::OnBootTimes(
BootTimesLoader::Handle handle, BootTimesLoader::BootTimes boot_times) {
const char* kBootTimesNoChromeExec =
"Non-firmware boot took %.2f seconds (kernel %.2fs, system %.2fs)";
const char* kBootTimesChromeExec =
"Non-firmware boot took %.2f seconds "
"(kernel %.2fs, system %.2fs, chrome %.2fs)";
std::string boot_times_text;
if (boot_times.chrome > 0) {
boot_times_text =
base::StringPrintf(
kBootTimesChromeExec,
boot_times.total,
boot_times.pre_startup,
boot_times.system,
boot_times.chrome);
} else {
boot_times_text =
base::StringPrintf(
kBootTimesNoChromeExec,
boot_times.total,
boot_times.pre_startup,
boot_times.system);
}
// Use UTF8ToWide once this string is localized.
boot_times_label_->SetText(ASCIIToWide(boot_times_text));
}
void BackgroundView::OnPolicyStateChanged(
policy::CloudPolicySubsystem::PolicySubsystemState state,
policy::CloudPolicySubsystem::ErrorDetails error_details) {
UpdateEnterpriseInfo();
}
} // namespace chromeos