blob: 296028e87a506f759d2c6baeec28f9476d33fa5a [file] [log] [blame]
// Copyright 2014 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/ui/views/profiles/profile_chooser_view.h"
#include "base/prefs/pref_service.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_info_cache.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_metrics.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_header_helper.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/signin/signin_promo.h"
#include "chrome/browser/signin/signin_ui_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/user_manager.h"
#include "chrome/browser/ui/views/profiles/user_manager_view.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/signin/core/browser/mutable_profile_oauth2_token_service.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
#include "components/signin/core/browser/signin_error_controller.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/signin/core/common/profile_management_switches.h"
#include "content/public/browser/render_widget_host_view.h"
#include "grit/theme_resources.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/path.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/text_elider.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/button/blue_button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
namespace {
// Helpers --------------------------------------------------------------------
const int kFixedMenuWidth = 250;
const int kButtonHeight = 32;
const int kFixedGaiaViewHeight = 440;
const int kFixedGaiaViewWidth = 360;
const int kFixedAccountRemovalViewWidth = 280;
const int kFixedSwitchUserViewWidth = 320;
const int kLargeImageSide = 88;
const int kVerticalSpacing = 16;
bool IsProfileChooser(profiles::BubbleViewMode mode) {
return mode == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER ||
mode == profiles::BUBBLE_VIEW_MODE_FAST_PROFILE_CHOOSER;
}
// Creates a GridLayout with a single column. This ensures that all the child
// views added get auto-expanded to fill the full width of the bubble.
views::GridLayout* CreateSingleColumnLayout(views::View* view, int width) {
views::GridLayout* layout = new views::GridLayout(view);
view->SetLayoutManager(layout);
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
views::GridLayout::FIXED, width, width);
return layout;
}
views::Link* CreateLink(const base::string16& link_text,
views::LinkListener* listener) {
views::Link* link_button = new views::Link(link_text);
link_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
link_button->SetUnderline(false);
link_button->set_listener(listener);
return link_button;
}
gfx::ImageSkia CreateSquarePlaceholderImage(int size) {
SkBitmap bitmap;
bitmap.allocPixels(SkImageInfo::MakeA8(size, size));
bitmap.eraseARGB(0, 0, 0, 0);
return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
}
bool HasAuthError(Profile* profile) {
const SigninErrorController* error =
profiles::GetSigninErrorController(profile);
return error && error->HasError();
}
std::string GetAuthErrorAccountId(Profile* profile) {
const SigninErrorController* error =
profiles::GetSigninErrorController(profile);
if (!error)
return std::string();
return error->error_account_id();
}
std::string GetAuthErrorUsername(Profile* profile) {
const SigninErrorController* error =
profiles::GetSigninErrorController(profile);
if (!error)
return std::string();
return error->error_username();
}
// BackgroundColorHoverButton -------------------------------------------------
// A custom button that allows for setting a background color when hovered over.
class BackgroundColorHoverButton : public views::LabelButton {
public:
BackgroundColorHoverButton(views::ButtonListener* listener,
const base::string16& text,
const gfx::ImageSkia& icon)
: views::LabelButton(listener, text) {
SetImageLabelSpacing(views::kItemLabelSpacing);
SetBorder(views::Border::CreateEmptyBorder(
0, views::kButtonHEdgeMarginNew, 0, views::kButtonHEdgeMarginNew));
SetMinSize(gfx::Size(0,
kButtonHeight + views::kRelatedControlVerticalSpacing));
SetImage(STATE_NORMAL, icon);
SetFocusable(true);
}
~BackgroundColorHoverButton() override {}
private:
// views::LabelButton:
void OnPaint(gfx::Canvas* canvas) override {
if ((state() == STATE_PRESSED) ||
(state() == STATE_HOVERED)) {
canvas->DrawColor(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ButtonHoverBackgroundColor));
}
LabelButton::OnPaint(canvas);
}
DISALLOW_COPY_AND_ASSIGN(BackgroundColorHoverButton);
};
// SizedContainer -------------------------------------------------
// A simple container view that takes an explicit preferred size.
class SizedContainer : public views::View {
public:
explicit SizedContainer(const gfx::Size& preferred_size)
: preferred_size_(preferred_size) {}
gfx::Size GetPreferredSize() const override { return preferred_size_; }
private:
gfx::Size preferred_size_;
};
} // namespace
// RightAlignedIconLabelButton -------------------------------------------------
// A custom LabelButton that has a centered text and right aligned icon.
class RightAlignedIconLabelButton : public views::LabelButton {
public:
RightAlignedIconLabelButton(views::ButtonListener* listener,
const base::string16& text)
: views::LabelButton(listener, text) {
}
protected:
void Layout() override {
// This layout trick keeps the text left-aligned and the icon right-aligned.
SetHorizontalAlignment(gfx::ALIGN_RIGHT);
views::LabelButton::Layout();
label()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
}
private:
DISALLOW_COPY_AND_ASSIGN(RightAlignedIconLabelButton);
};
// EditableProfilePhoto -------------------------------------------------
// A custom Image control that shows a "change" button when moused over.
class EditableProfilePhoto : public views::LabelButton {
public:
EditableProfilePhoto(views::ButtonListener* listener,
const gfx::Image& icon,
bool is_editing_allowed,
const gfx::Rect& bounds)
: views::LabelButton(listener, base::string16()),
photo_overlay_(NULL) {
gfx::Image image = profiles::GetSizedAvatarIcon(
icon, true, kLargeImageSide, kLargeImageSide);
SetImage(views::LabelButton::STATE_NORMAL, *image.ToImageSkia());
SetBorder(views::Border::NullBorder());
SetBoundsRect(bounds);
// Calculate the circular mask that will be used to display the photo.
circular_mask_.addCircle(SkIntToScalar(bounds.width() / 2),
SkIntToScalar(bounds.height() / 2),
SkIntToScalar(bounds.width() / 2));
if (!is_editing_allowed) {
SetEnabled(false);
return;
}
set_notify_enter_exit_on_child(true);
// Photo overlay that appears when hovering over the button.
photo_overlay_ = new views::ImageView();
const SkColor kBackgroundColor = SkColorSetARGB(65, 255, 255, 255);
photo_overlay_->set_background(
views::Background::CreateSolidBackground(kBackgroundColor));
photo_overlay_->SetImage(*ui::ResourceBundle::GetSharedInstance().
GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_CAMERA));
photo_overlay_->SetSize(bounds.size());
photo_overlay_->SetVisible(false);
AddChildView(photo_overlay_);
}
void OnPaint(gfx::Canvas* canvas) override {
// Display the profile picture as a circle.
canvas->ClipPath(circular_mask_, true);
views::LabelButton::OnPaint(canvas);
}
void PaintChildren(gfx::Canvas* canvas,
const views::CullSet& cull_set) override {
// Display any children (the "change photo" overlay) as a circle.
canvas->ClipPath(circular_mask_, true);
View::PaintChildren(canvas, cull_set);
}
private:
// views::CustomButton:
void StateChanged() override {
bool show_overlay =
(state() == STATE_PRESSED || state() == STATE_HOVERED || HasFocus());
if (photo_overlay_)
photo_overlay_->SetVisible(show_overlay);
}
void OnFocus() override {
views::LabelButton::OnFocus();
if (photo_overlay_)
photo_overlay_->SetVisible(true);
}
void OnBlur() override {
views::LabelButton::OnBlur();
// Don't hide the overlay if it's being shown as a result of a mouseover.
if (photo_overlay_ && state() != STATE_HOVERED)
photo_overlay_->SetVisible(false);
}
gfx::Path circular_mask_;
// Image that is shown when hovering over the image button. Can be NULL if
// the photo isn't allowed to be edited (e.g. for guest profiles).
views::ImageView* photo_overlay_;
DISALLOW_COPY_AND_ASSIGN(EditableProfilePhoto);
};
// EditableProfileName -------------------------------------------------
// A custom text control that turns into a textfield for editing when clicked.
class EditableProfileName : public RightAlignedIconLabelButton,
public views::ButtonListener {
public:
EditableProfileName(views::TextfieldController* controller,
const base::string16& text,
bool is_editing_allowed)
: RightAlignedIconLabelButton(this, text),
profile_name_textfield_(NULL) {
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
const gfx::FontList& medium_font_list =
rb->GetFontList(ui::ResourceBundle::MediumFont);
SetFontList(medium_font_list);
SetHorizontalAlignment(gfx::ALIGN_CENTER);
if (!is_editing_allowed) {
SetBorder(views::Border::CreateEmptyBorder(2, 0, 2, 0));
return;
}
// Show an "edit" pencil icon when hovering over. In the default state,
// we need to create an empty placeholder of the correct size, so that
// the text doesn't jump around when the hovered icon appears.
gfx::ImageSkia hover_image =
*rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_HOVER);
SetImage(STATE_NORMAL, CreateSquarePlaceholderImage(hover_image.width()));
SetImage(STATE_HOVERED, hover_image);
SetImage(STATE_PRESSED,
*rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_PRESSED));
// To center the text, we need to offest it by the width of the icon we
// are adding and its padding. We need to also add a small top/bottom
// padding to account for the textfield's border.
const int kIconTextLabelButtonSpacing = 5;
SetBorder(views::Border::CreateEmptyBorder(
2, hover_image.width() + kIconTextLabelButtonSpacing, 2, 0));
// Textfield that overlaps the button.
profile_name_textfield_ = new views::Textfield();
profile_name_textfield_->set_controller(controller);
profile_name_textfield_->SetFontList(medium_font_list);
profile_name_textfield_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
profile_name_textfield_->SetVisible(false);
AddChildView(profile_name_textfield_);
}
views::Textfield* profile_name_textfield() {
return profile_name_textfield_;
}
// Hide the editable textfield to show the profile name button instead.
void ShowReadOnlyView() {
if (profile_name_textfield_)
profile_name_textfield_->SetVisible(false);
}
private:
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override {
if (profile_name_textfield_) {
profile_name_textfield_->SetVisible(true);
profile_name_textfield_->SetText(GetText());
profile_name_textfield_->SelectAll(false);
profile_name_textfield_->RequestFocus();
}
}
// views::LabelButton:
bool OnKeyReleased(const ui::KeyEvent& event) override {
// Override CustomButton's implementation, which presses the button when
// you press space and clicks it when you release space, as the space can be
// part of the new profile name typed in the textfield.
return false;
}
void Layout() override {
if (profile_name_textfield_)
profile_name_textfield_->SetBounds(0, 0, width(), height());
RightAlignedIconLabelButton::Layout();
}
void OnFocus() override {
RightAlignedIconLabelButton::OnFocus();
SetState(STATE_HOVERED);
}
void OnBlur() override {
RightAlignedIconLabelButton::OnBlur();
SetState(STATE_NORMAL);
}
// Textfield that is shown when editing the profile name. Can be NULL if
// the profile name isn't allowed to be edited (e.g. for guest profiles).
views::Textfield* profile_name_textfield_;
DISALLOW_COPY_AND_ASSIGN(EditableProfileName);
};
// A title card with one back button right aligned and one label center aligned.
class TitleCard : public views::View {
public:
TitleCard(const base::string16& message, views::ButtonListener* listener,
views::ImageButton** back_button) {
back_button_ = new views::ImageButton(listener);
back_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
views::ImageButton::ALIGN_MIDDLE);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
back_button_->SetImage(views::ImageButton::STATE_NORMAL,
rb->GetImageSkiaNamed(IDR_BACK));
back_button_->SetImage(views::ImageButton::STATE_HOVERED,
rb->GetImageSkiaNamed(IDR_BACK_H));
back_button_->SetImage(views::ImageButton::STATE_PRESSED,
rb->GetImageSkiaNamed(IDR_BACK_P));
back_button_->SetImage(views::ImageButton::STATE_DISABLED,
rb->GetImageSkiaNamed(IDR_BACK_D));
back_button_->SetFocusable(true);
*back_button = back_button_;
title_label_ = new views::Label(message);
title_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
const gfx::FontList& medium_font_list =
rb->GetFontList(ui::ResourceBundle::MediumFont);
title_label_->SetFontList(medium_font_list);
AddChildView(back_button_);
AddChildView(title_label_);
}
// Creates a new view that has the |title_card| with horizontal padding at the
// top, an edge-to-edge separator below, and the specified |view| at the
// bottom.
static views::View* AddPaddedTitleCard(views::View* view,
TitleCard* title_card,
int width) {
views::View* titled_view = new views::View();
views::GridLayout* layout = new views::GridLayout(titled_view);
titled_view->SetLayoutManager(layout);
// Column set 0 is a single column layout with horizontal padding at left
// and right, and column set 1 is a single column layout with no padding.
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew);
int available_width = width - 2 * views::kButtonHEdgeMarginNew;
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
views::GridLayout::FIXED, available_width, available_width);
columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew);
layout->AddColumnSet(1)->AddColumn(views::GridLayout::FILL,
views::GridLayout::FILL, 0,views::GridLayout::FIXED, width, width);
layout->StartRowWithPadding(1, 0, 0, kVerticalSpacing);
layout->AddView(title_card);
layout->StartRowWithPadding(1, 1, 0, kVerticalSpacing);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
layout->StartRow(1, 1);
layout->AddView(view);
return titled_view;
}
private:
void Layout() override {
int back_button_width = back_button_->GetPreferredSize().width();
back_button_->SetBounds(0, 0, back_button_width, height());
int label_padding = back_button_width + views::kButtonHEdgeMarginNew;
int label_width = width() - 2 * label_padding;
DCHECK_GT(label_width, 0);
title_label_->SetBounds(label_padding, 0, label_width, height());
}
gfx::Size GetPreferredSize() const override {
int height = std::max(title_label_->GetPreferredSize().height(),
back_button_->GetPreferredSize().height());
return gfx::Size(width(), height);
}
views::ImageButton* back_button_;
views::Label* title_label_;
DISALLOW_COPY_AND_ASSIGN(TitleCard);
};
// ProfileChooserView ---------------------------------------------------------
// static
ProfileChooserView* ProfileChooserView::profile_bubble_ = NULL;
bool ProfileChooserView::close_on_deactivate_for_testing_ = true;
// static
void ProfileChooserView::ShowBubble(
profiles::BubbleViewMode view_mode,
profiles::TutorialMode tutorial_mode,
const signin::ManageAccountsParams& manage_accounts_params,
views::View* anchor_view,
views::BubbleBorder::Arrow arrow,
views::BubbleBorder::BubbleAlignment border_alignment,
Browser* browser) {
if (IsShowing()) {
if (tutorial_mode != profiles::TUTORIAL_MODE_NONE) {
profile_bubble_->tutorial_mode_ = tutorial_mode;
profile_bubble_->ShowView(view_mode, profile_bubble_->avatar_menu_.get());
}
return;
}
profile_bubble_ = new ProfileChooserView(anchor_view, arrow, browser,
view_mode, tutorial_mode, manage_accounts_params.service_type);
views::BubbleDelegateView::CreateBubble(profile_bubble_);
profile_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_);
profile_bubble_->SetAlignment(border_alignment);
profile_bubble_->GetWidget()->Show();
profile_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
}
// static
bool ProfileChooserView::IsShowing() {
return profile_bubble_ != NULL;
}
// static
void ProfileChooserView::Hide() {
if (IsShowing())
profile_bubble_->GetWidget()->Close();
}
ProfileChooserView::ProfileChooserView(views::View* anchor_view,
views::BubbleBorder::Arrow arrow,
Browser* browser,
profiles::BubbleViewMode view_mode,
profiles::TutorialMode tutorial_mode,
signin::GAIAServiceType service_type)
: BubbleDelegateView(anchor_view, arrow),
browser_(browser),
view_mode_(view_mode),
tutorial_mode_(tutorial_mode),
gaia_service_type_(service_type) {
// Reset the default margins inherited from the BubbleDelegateView.
// Add a small bottom inset so that the bubble's rounded corners show up.
set_margins(gfx::Insets(0, 0, 1, 0));
set_background(views::Background::CreateSolidBackground(
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DialogBackground)));
ResetView();
avatar_menu_.reset(new AvatarMenu(
&g_browser_process->profile_manager()->GetProfileInfoCache(),
this,
browser_));
avatar_menu_->RebuildMenu();
ProfileOAuth2TokenService* oauth2_token_service =
ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
if (oauth2_token_service)
oauth2_token_service->AddObserver(this);
}
ProfileChooserView::~ProfileChooserView() {
ProfileOAuth2TokenService* oauth2_token_service =
ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
if (oauth2_token_service)
oauth2_token_service->RemoveObserver(this);
}
void ProfileChooserView::ResetView() {
open_other_profile_indexes_map_.clear();
delete_account_button_map_.clear();
reauth_account_button_map_.clear();
manage_accounts_link_ = NULL;
signin_current_profile_link_ = NULL;
auth_error_email_button_ = NULL;
current_profile_photo_ = NULL;
current_profile_name_ = NULL;
users_button_ = NULL;
go_incognito_button_ = NULL;
lock_button_ = NULL;
add_account_link_ = NULL;
gaia_signin_cancel_button_ = NULL;
remove_account_button_ = NULL;
account_removal_cancel_button_ = NULL;
add_person_button_ = NULL;
disconnect_button_ = NULL;
switch_user_cancel_button_ = NULL;
tutorial_sync_settings_ok_button_ = NULL;
tutorial_close_button_ = NULL;
tutorial_sync_settings_link_ = NULL;
tutorial_see_whats_new_button_ = NULL;
tutorial_not_you_link_ = NULL;
tutorial_learn_more_link_ = NULL;
}
void ProfileChooserView::Init() {
// If view mode is PROFILE_CHOOSER but there is an auth error, force
// ACCOUNT_MANAGEMENT mode.
if (IsProfileChooser(view_mode_) &&
HasAuthError(browser_->profile()) &&
switches::IsEnableAccountConsistency() &&
avatar_menu_->GetItemAt(avatar_menu_->GetActiveProfileIndex()).
signed_in) {
view_mode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT;
}
// The arrow keys can be used to tab between items.
AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE));
AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE));
ShowView(view_mode_, avatar_menu_.get());
}
void ProfileChooserView::OnAvatarMenuChanged(
AvatarMenu* avatar_menu) {
if (IsProfileChooser(view_mode_) ||
view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) {
// Refresh the view with the new menu. We can't just update the local copy
// as this may have been triggered by a sign out action, in which case
// the view is being destroyed.
ShowView(view_mode_, avatar_menu);
}
}
void ProfileChooserView::OnRefreshTokenAvailable(
const std::string& account_id) {
if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ||
view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
// The account management UI is only available through the
// --enable-account-consistency flag.
ShowView(switches::IsEnableAccountConsistency() ?
profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
}
}
void ProfileChooserView::OnRefreshTokenRevoked(const std::string& account_id) {
// Refresh the account management view when an account is removed from the
// profile.
if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT)
ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
}
void ProfileChooserView::ShowView(profiles::BubbleViewMode view_to_display,
AvatarMenu* avatar_menu) {
// The account management view should only be displayed if the active profile
// is signed in.
if (view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) {
DCHECK(switches::IsEnableAccountConsistency());
const AvatarMenu::Item& active_item = avatar_menu->GetItemAt(
avatar_menu->GetActiveProfileIndex());
DCHECK(active_item.signed_in);
}
if (browser_->profile()->IsSupervised() &&
(view_to_display == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL)) {
LOG(WARNING) << "Supervised user attempted to add/remove account";
return;
}
ResetView();
RemoveAllChildViews(true);
view_mode_ = view_to_display;
views::GridLayout* layout;
views::View* sub_view;
switch (view_mode_) {
case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH:
layout = CreateSingleColumnLayout(this, kFixedGaiaViewWidth);
sub_view = CreateGaiaSigninView();
break;
case profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL:
layout = CreateSingleColumnLayout(this, kFixedAccountRemovalViewWidth);
sub_view = CreateAccountRemovalView();
break;
case profiles::BUBBLE_VIEW_MODE_SWITCH_USER:
layout = CreateSingleColumnLayout(this, kFixedSwitchUserViewWidth);
sub_view = CreateSwitchUserView();
ProfileMetrics::LogProfileNewAvatarMenuNotYou(
ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_VIEW);
break;
default:
layout = CreateSingleColumnLayout(this, kFixedMenuWidth);
sub_view = CreateProfileChooserView(avatar_menu);
}
// Clears tutorial mode for all non-profile-chooser views.
if (view_mode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER)
tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
layout->StartRow(1, 0);
layout->AddView(sub_view);
Layout();
if (GetBubbleFrameView())
SizeToContents();
}
void ProfileChooserView::WindowClosing() {
DCHECK_EQ(profile_bubble_, this);
profile_bubble_ = NULL;
if (tutorial_mode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
LoginUIServiceFactory::GetForProfile(browser_->profile())->
SyncConfirmationUIClosed(false /* configure_sync_first */);
}
}
bool ProfileChooserView::AcceleratorPressed(
const ui::Accelerator& accelerator) {
if (accelerator.key_code() != ui::VKEY_DOWN &&
accelerator.key_code() != ui::VKEY_UP)
return BubbleDelegateView::AcceleratorPressed(accelerator);
// Move the focus up or down.
GetFocusManager()->AdvanceFocus(accelerator.key_code() != ui::VKEY_DOWN);
return true;
}
bool ProfileChooserView::HandleContextMenu(
const content::ContextMenuParams& params) {
// Suppresses the context menu because some features, such as inspecting
// elements, are not appropriate in a bubble.
return true;
}
void ProfileChooserView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender == users_button_) {
// If this is a guest session, close all the guest browser windows.
if (browser_->profile()->IsGuestSession()) {
profiles::CloseGuestProfileWindows();
} else {
UserManager::Show(base::FilePath(),
profiles::USER_MANAGER_NO_TUTORIAL,
profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
}
PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_OPEN_USER_MANAGER);
} else if (sender == go_incognito_button_) {
DCHECK(ShouldShowGoIncognito());
chrome::NewIncognitoWindow(browser_);
PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_GO_INCOGNITO);
} else if (sender == lock_button_) {
profiles::LockProfile(browser_->profile());
PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_LOCK);
} else if (sender == auth_error_email_button_) {
ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get());
} else if (sender == tutorial_sync_settings_ok_button_) {
LoginUIServiceFactory::GetForProfile(browser_->profile())->
SyncConfirmationUIClosed(false /* configure_sync_first */);
DismissTutorial();
ProfileMetrics::LogProfileNewAvatarMenuSignin(
ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_OK);
} else if (sender == tutorial_close_button_) {
DCHECK(tutorial_mode_ != profiles::TUTORIAL_MODE_NONE &&
tutorial_mode_ != profiles::TUTORIAL_MODE_CONFIRM_SIGNIN);
DismissTutorial();
} else if (sender == tutorial_see_whats_new_button_) {
ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_WHATS_NEW);
UserManager::Show(base::FilePath(),
profiles::USER_MANAGER_TUTORIAL_OVERVIEW,
profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
} else if (sender == remove_account_button_) {
RemoveAccount();
} else if (sender == account_removal_cancel_button_) {
account_id_to_remove_.clear();
ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
} else if (sender == gaia_signin_cancel_button_) {
// The account management view is only available with the
// --enable-account-consistency flag.
bool account_management_available =
SigninManagerFactory::GetForProfile(browser_->profile())->
IsAuthenticated() &&
switches::IsEnableAccountConsistency();
ShowView(account_management_available ?
profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
} else if (sender == current_profile_photo_) {
avatar_menu_->EditProfile(avatar_menu_->GetActiveProfileIndex());
PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE);
} else if (sender == signin_current_profile_link_) {
ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN, avatar_menu_.get());
} else if (sender == add_person_button_) {
ProfileMetrics::LogProfileNewAvatarMenuNotYou(
ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_ADD_PERSON);
UserManager::Show(base::FilePath(),
profiles::USER_MANAGER_NO_TUTORIAL,
profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
} else if (sender == disconnect_button_) {
ProfileMetrics::LogProfileNewAvatarMenuNotYou(
ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_DISCONNECT);
chrome::ShowSettings(browser_);
} else if (sender == switch_user_cancel_button_) {
ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
ProfileMetrics::LogProfileNewAvatarMenuNotYou(
ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_BACK);
} else {
// Either one of the "other profiles", or one of the profile accounts
// buttons was pressed.
ButtonIndexes::const_iterator profile_match =
open_other_profile_indexes_map_.find(sender);
if (profile_match != open_other_profile_indexes_map_.end()) {
avatar_menu_->SwitchToProfile(
profile_match->second,
ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW,
ProfileMetrics::SWITCH_PROFILE_ICON);
} else {
// This was a profile accounts button.
AccountButtonIndexes::const_iterator account_match =
delete_account_button_map_.find(sender);
if (account_match != delete_account_button_map_.end()) {
account_id_to_remove_ = account_match->second;
ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL,
avatar_menu_.get());
} else {
account_match = reauth_account_button_map_.find(sender);
DCHECK(account_match != reauth_account_button_map_.end());
ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get());
}
}
}
}
void ProfileChooserView::RemoveAccount() {
DCHECK(!account_id_to_remove_.empty());
MutableProfileOAuth2TokenService* oauth2_token_service =
ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(
browser_->profile());
if (oauth2_token_service) {
oauth2_token_service->RevokeCredentials(account_id_to_remove_);
PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_REMOVE_ACCT);
}
account_id_to_remove_.clear();
ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
}
void ProfileChooserView::LinkClicked(views::Link* sender, int event_flags) {
if (sender == manage_accounts_link_) {
// This link can either mean show/hide the account management view,
// depending on which view it is displayed. ShowView() will DCHECK if
// the account management view is displayed for non signed-in users.
ShowView(
view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ?
profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER :
profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT,
avatar_menu_.get());
} else if (sender == add_account_link_) {
ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT, avatar_menu_.get());
PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT);
} else if (sender == tutorial_sync_settings_link_) {
LoginUIServiceFactory::GetForProfile(browser_->profile())->
SyncConfirmationUIClosed(true /* configure_sync_first */);
tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
ProfileMetrics::LogProfileNewAvatarMenuSignin(
ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_SETTINGS);
} else if (sender == tutorial_not_you_link_) {
ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_NOT_YOU);
ShowView(profiles::BUBBLE_VIEW_MODE_SWITCH_USER, avatar_menu_.get());
} else {
DCHECK(sender == tutorial_learn_more_link_);
signin_ui_util::ShowSigninErrorLearnMorePage(browser_->profile());
}
}
void ProfileChooserView::StyledLabelLinkClicked(
const gfx::Range& range, int event_flags) {
chrome::ShowSettings(browser_);
}
bool ProfileChooserView::HandleKeyEvent(views::Textfield* sender,
const ui::KeyEvent& key_event) {
views::Textfield* name_textfield =
current_profile_name_->profile_name_textfield();
DCHECK(sender == name_textfield);
if (key_event.key_code() == ui::VKEY_RETURN ||
key_event.key_code() == ui::VKEY_TAB) {
// Pressing Tab/Enter commits the new profile name, unless it's empty.
base::string16 new_profile_name = name_textfield->text();
base::TrimWhitespace(new_profile_name, base::TRIM_ALL, &new_profile_name);
if (new_profile_name.empty())
return true;
const AvatarMenu::Item& active_item = avatar_menu_->GetItemAt(
avatar_menu_->GetActiveProfileIndex());
Profile* profile = g_browser_process->profile_manager()->GetProfile(
active_item.profile_path);
DCHECK(profile);
if (profile->IsLegacySupervised())
return true;
profiles::UpdateProfileName(profile, new_profile_name);
PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME);
current_profile_name_->ShowReadOnlyView();
return true;
}
return false;
}
void ProfileChooserView::PopulateCompleteProfileChooserView(
views::GridLayout* layout,
AvatarMenu* avatar_menu) {
// Separate items into active and alternatives.
Indexes other_profiles;
views::View* tutorial_view = NULL;
views::View* current_profile_view = NULL;
views::View* current_profile_accounts = NULL;
views::View* option_buttons_view = NULL;
for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) {
const AvatarMenu::Item& item = avatar_menu->GetItemAt(i);
if (item.active) {
option_buttons_view = CreateOptionsView(
item.signed_in && profiles::IsLockAvailable(browser_->profile()));
current_profile_view = CreateCurrentProfileView(item, false);
if (IsProfileChooser(view_mode_)) {
switch (tutorial_mode_) {
case profiles::TUTORIAL_MODE_NONE:
case profiles::TUTORIAL_MODE_WELCOME_UPGRADE:
tutorial_view = CreateWelcomeUpgradeTutorialViewIfNeeded(
tutorial_mode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE,
item);
break;
case profiles::TUTORIAL_MODE_CONFIRM_SIGNIN:
tutorial_view = CreateSigninConfirmationView();
break;
case profiles::TUTORIAL_MODE_SHOW_ERROR:
tutorial_view = CreateSigninErrorView();
break;
}
} else {
current_profile_accounts = CreateCurrentProfileAccountsView(item);
}
} else {
other_profiles.push_back(i);
}
}
if (tutorial_view) {
// TODO(mlerman): update UMA stats for the new tutorial.
layout->StartRow(1, 0);
layout->AddView(tutorial_view);
} else {
tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
}
if (!current_profile_view) {
// Guest windows don't have an active profile.
current_profile_view = CreateGuestProfileView();
option_buttons_view = CreateOptionsView(false);
}
layout->StartRow(1, 0);
layout->AddView(current_profile_view);
if (!IsProfileChooser(view_mode_)) {
DCHECK(current_profile_accounts);
layout->StartRow(0, 0);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
layout->StartRow(1, 0);
layout->AddView(current_profile_accounts);
}
if (browser_->profile()->IsSupervised()) {
layout->StartRow(0, 0);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
layout->StartRow(1, 0);
layout->AddView(CreateSupervisedUserDisclaimerView());
}
if (IsProfileChooser(view_mode_)) {
layout->StartRow(1, 0);
if (switches::IsFastUserSwitching()) {
layout->AddView(CreateOtherProfilesView(other_profiles));
}
}
layout->StartRow(0, 0);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
if (option_buttons_view) {
layout->StartRow(0, 0);
layout->AddView(option_buttons_view);
}
}
void ProfileChooserView::PopulateMinimalProfileChooserView(
views::GridLayout* layout,
AvatarMenu* avatar_menu) {
Indexes other_profiles;
for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) {
const AvatarMenu::Item& item = avatar_menu->GetItemAt(i);
if (!item.active) {
other_profiles.push_back(i);
}
}
layout->StartRow(1, 0);
layout->AddView(CreateOtherProfilesView(other_profiles));
}
views::View* ProfileChooserView::CreateProfileChooserView(
AvatarMenu* avatar_menu) {
views::View* view = new views::View();
views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
if (view_mode_ == profiles::BUBBLE_VIEW_MODE_FAST_PROFILE_CHOOSER)
PopulateMinimalProfileChooserView(layout, avatar_menu);
else
PopulateCompleteProfileChooserView(layout, avatar_menu);
return view;
}
void ProfileChooserView::DismissTutorial() {
// Never shows the upgrade tutorial again if manually closed.
if (tutorial_mode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
browser_->profile()->GetPrefs()->SetInteger(
prefs::kProfileAvatarTutorialShown,
signin_ui_util::kUpgradeWelcomeTutorialShowMax + 1);
}
tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
}
views::View* ProfileChooserView::CreateTutorialView(
profiles::TutorialMode tutorial_mode,
const base::string16& title_text,
const base::string16& content_text,
const base::string16& link_text,
const base::string16& button_text,
bool stack_button,
views::Link** link,
views::LabelButton** button,
views::ImageButton** close_button) {
tutorial_mode_ = tutorial_mode;
views::View* view = new views::View();
view->set_background(views::Background::CreateSolidBackground(
profiles::kAvatarTutorialBackgroundColor));
views::GridLayout* layout = CreateSingleColumnLayout(view,
kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew);
// Creates a second column set for buttons and links.
views::ColumnSet* button_columns = layout->AddColumnSet(1);
button_columns->AddColumn(views::GridLayout::LEADING,
views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
button_columns->AddPaddingColumn(
1, views::kUnrelatedControlHorizontalSpacing);
button_columns->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
layout->SetInsets(views::kButtonVEdgeMarginNew,
views::kButtonHEdgeMarginNew,
views::kButtonVEdgeMarginNew,
views::kButtonHEdgeMarginNew);
// Adds title and close button if needed.
views::Label* title_label = new views::Label(title_text);
title_label->SetMultiLine(true);
title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title_label->SetAutoColorReadabilityEnabled(false);
title_label->SetEnabledColor(SK_ColorWHITE);
title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
ui::ResourceBundle::MediumFont));
if (close_button) {
layout->StartRow(1, 1);
layout->AddView(title_label);
*close_button = new views::ImageButton(this);
(*close_button)->SetImageAlignment(views::ImageButton::ALIGN_RIGHT,
views::ImageButton::ALIGN_MIDDLE);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
(*close_button)->SetImage(views::ImageButton::STATE_NORMAL,
rb->GetImageSkiaNamed(IDR_CLOSE_1));
(*close_button)->SetImage(views::ImageButton::STATE_HOVERED,
rb->GetImageSkiaNamed(IDR_CLOSE_1_H));
(*close_button)->SetImage(views::ImageButton::STATE_PRESSED,
rb->GetImageSkiaNamed(IDR_CLOSE_1_P));
layout->AddView(*close_button);
} else {
layout->StartRow(1, 0);
layout->AddView(title_label);
}
// Adds body content.
views::Label* content_label = new views::Label(content_text);
content_label->SetMultiLine(true);
content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
content_label->SetAutoColorReadabilityEnabled(false);
content_label->SetEnabledColor(profiles::kAvatarTutorialContentTextColor);
layout->StartRowWithPadding(1, 0, 0, views::kRelatedControlVerticalSpacing);
layout->AddView(content_label);
// Adds links and buttons.
bool has_button = !button_text.empty();
if (has_button) {
*button = new views::LabelButton(this, button_text);
(*button)->SetHorizontalAlignment(gfx::ALIGN_CENTER);
(*button)->SetStyle(views::Button::STYLE_BUTTON);
}
bool has_link = !link_text.empty();
if (has_link) {
*link = CreateLink(link_text, this);
(*link)->SetHorizontalAlignment(gfx::ALIGN_LEFT);
(*link)->SetAutoColorReadabilityEnabled(false);
(*link)->SetEnabledColor(SK_ColorWHITE);
}
if (stack_button) {
DCHECK(has_button);
layout->StartRowWithPadding(
1, 0, 0, views::kUnrelatedControlVerticalSpacing);
layout->AddView(*button);
if (has_link) {
layout->StartRowWithPadding(
1, 0, 0, views::kRelatedControlVerticalSpacing);
(*link)->SetHorizontalAlignment(gfx::ALIGN_CENTER);
layout->AddView(*link);
}
} else {
DCHECK(has_link || has_button);
layout->StartRowWithPadding(
1, 1, 0, views::kUnrelatedControlVerticalSpacing);
if (has_link)
layout->AddView(*link);
else
layout->SkipColumns(1);
if (has_button)
layout->AddView(*button);
else
layout->SkipColumns(1);
}
return view;
}
views::View* ProfileChooserView::CreateCurrentProfileView(
const AvatarMenu::Item& avatar_item,
bool is_guest) {
views::View* view = new views::View();
int column_width = kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew;
views::GridLayout* layout = CreateSingleColumnLayout(view, column_width);
layout->SetInsets(views::kButtonVEdgeMarginNew,
views::kButtonHEdgeMarginNew,
views::kUnrelatedControlVerticalSpacing,
views::kButtonHEdgeMarginNew);
// Profile icon, centered.
int x_offset = (column_width - kLargeImageSide) / 2;
current_profile_photo_ = new EditableProfilePhoto(
this, avatar_item.icon, !is_guest,
gfx::Rect(x_offset, 0, kLargeImageSide, kLargeImageSide));
SizedContainer* profile_icon_container =
new SizedContainer(gfx::Size(column_width, kLargeImageSide));
profile_icon_container->AddChildView(current_profile_photo_);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
if (browser_->profile()->IsSupervised()) {
views::ImageView* supervised_icon = new views::ImageView();
int image_id = browser_->profile()->IsChild()
? IDR_ICON_PROFILES_MENU_CHILD
: IDR_ICON_PROFILES_MENU_LEGACY_SUPERVISED;
supervised_icon->SetImage(rb->GetImageSkiaNamed(image_id));
gfx::Size preferred_size = supervised_icon->GetPreferredSize();
gfx::Rect parent_bounds = current_profile_photo_->bounds();
supervised_icon->SetBounds(
parent_bounds.right() - preferred_size.width(),
parent_bounds.bottom() - preferred_size.height(),
preferred_size.width(),
preferred_size.height());
profile_icon_container->AddChildView(supervised_icon);
}
layout->StartRow(1, 0);
layout->AddView(profile_icon_container);
// Profile name, centered.
bool editing_allowed = !is_guest &&
!browser_->profile()->IsLegacySupervised();
current_profile_name_ = new EditableProfileName(
this,
profiles::GetAvatarNameForProfile(browser_->profile()->GetPath()),
editing_allowed);
layout->StartRowWithPadding(1, 0, 0,
views::kRelatedControlSmallVerticalSpacing);
layout->StartRow(1, 0);
layout->AddView(current_profile_name_);
if (is_guest)
return view;
// The available links depend on the type of profile that is active.
if (avatar_item.signed_in) {
layout->StartRow(1, 0);
if (switches::IsEnableAccountConsistency()) {
base::string16 link_title = l10n_util::GetStringUTF16(
IsProfileChooser(view_mode_) ?
IDS_PROFILES_PROFILE_MANAGE_ACCOUNTS_BUTTON :
IDS_PROFILES_PROFILE_HIDE_MANAGE_ACCOUNTS_BUTTON);
manage_accounts_link_ = CreateLink(link_title, this);
manage_accounts_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
layout->AddView(manage_accounts_link_);
} else {
// Badge the email address if there's an authentication error.
if (HasAuthError(browser_->profile())) {
const gfx::ImageSkia warning_image = *rb->GetImageNamed(
IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia();
auth_error_email_button_ =
new RightAlignedIconLabelButton(this, avatar_item.sync_state);
auth_error_email_button_->SetElideBehavior(gfx::ELIDE_EMAIL);
auth_error_email_button_->SetImage(
views::LabelButton::STATE_NORMAL, warning_image);
auth_error_email_button_->SetTextColor(
views::LabelButton::STATE_NORMAL,
views::Link::GetDefaultEnabledColor());
auth_error_email_button_->SetFocusable(true);
gfx::Insets insets = views::LabelButtonBorder::GetDefaultInsetsForStyle(
views::Button::STYLE_TEXTBUTTON);
auth_error_email_button_->SetBorder(views::Border::CreateEmptyBorder(
insets.top(), insets.left(), insets.bottom(), insets.right()));
layout->AddView(auth_error_email_button_);
} else {
// Add a small padding between the email button and the profile name.
layout->StartRowWithPadding(1, 0, 0, 2);
views::Label* email_label = new views::Label(avatar_item.sync_state);
email_label->SetElideBehavior(gfx::ELIDE_EMAIL);
email_label->SetEnabled(false);
layout->AddView(email_label);
}
}
} else {
SigninManagerBase* signin_manager = SigninManagerFactory::GetForProfile(
browser_->profile()->GetOriginalProfile());
if (signin_manager->IsSigninAllowed()) {
views::Label* promo = new views::Label(
l10n_util::GetStringUTF16(IDS_PROFILES_SIGNIN_PROMO));
promo->SetMultiLine(true);
promo->SetHorizontalAlignment(gfx::ALIGN_LEFT);
layout->StartRowWithPadding(1, 0, 0,
views::kRelatedControlSmallVerticalSpacing);
layout->StartRow(1, 0);
layout->AddView(promo);
signin_current_profile_link_ = new views::BlueButton(
this, l10n_util::GetStringFUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL,
l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)));
layout->StartRowWithPadding(1, 0, 0,
views::kRelatedControlVerticalSpacing);
layout->StartRow(1, 0);
layout->AddView(signin_current_profile_link_);
}
}
return view;
}
views::View* ProfileChooserView::CreateGuestProfileView() {
gfx::Image guest_icon =
ui::ResourceBundle::GetSharedInstance().GetImageNamed(
profiles::GetPlaceholderAvatarIconResourceID());
AvatarMenu::Item guest_avatar_item(0, 0, guest_icon);
guest_avatar_item.active = true;
guest_avatar_item.name = l10n_util::GetStringUTF16(
IDS_PROFILES_GUEST_PROFILE_NAME);
guest_avatar_item.signed_in = false;
return CreateCurrentProfileView(guest_avatar_item, true);
}
views::View* ProfileChooserView::CreateOtherProfilesView(
const Indexes& avatars_to_show) {
views::View* view = new views::View();
views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
int num_avatars_to_show = avatars_to_show.size();
for (int i = 0; i < num_avatars_to_show; ++i) {
const size_t index = avatars_to_show[i];
const AvatarMenu::Item& item = avatar_menu_->GetItemAt(index);
const int kSmallImageSide = 32;
// Use the low-res, small default avatars in the fast user switcher, like
// we do in the menu bar.
gfx::Image item_icon;
bool is_rectangle;
AvatarMenu::GetImageForMenuButton(
item.profile_path, &item_icon, &is_rectangle);
gfx::Image image = profiles::GetSizedAvatarIcon(
item_icon, true, kSmallImageSide, kSmallImageSide);
views::LabelButton* button = new BackgroundColorHoverButton(
this,
item.name,
*image.ToImageSkia());
open_other_profile_indexes_map_[button] = index;
layout->StartRow(1, 0);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
layout->StartRow(1, 0);
layout->AddView(button);
}
return view;
}
views::View* ProfileChooserView::CreateOptionsView(bool display_lock) {
views::View* view = new views::View();
views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
base::string16 text = browser_->profile()->IsGuestSession() ?
l10n_util::GetStringUTF16(IDS_PROFILES_EXIT_GUEST) :
l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_USERS_BUTTON);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
users_button_ = new BackgroundColorHoverButton(
this,
text,
*rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
layout->StartRow(1, 0);
layout->AddView(users_button_);
if (ShouldShowGoIncognito()) {
layout->StartRow(1, 0);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
go_incognito_button_ = new BackgroundColorHoverButton(
this,
l10n_util::GetStringUTF16(IDS_PROFILES_GO_INCOGNITO_BUTTON),
*rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_INCOGNITO));
layout->StartRow(1, 0);
layout->AddView(go_incognito_button_);
}
if (display_lock) {
layout->StartRow(1, 0);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
lock_button_ = new BackgroundColorHoverButton(
this,
l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON),
*rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_LOCK));
layout->StartRow(1, 0);
layout->AddView(lock_button_);
}
return view;
}
views::View* ProfileChooserView::CreateSupervisedUserDisclaimerView() {
views::View* view = new views::View();
views::GridLayout* layout = CreateSingleColumnLayout(
view, kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew);
layout->SetInsets(views::kRelatedControlVerticalSpacing,
views::kButtonHEdgeMarginNew,
views::kRelatedControlVerticalSpacing,
views::kButtonHEdgeMarginNew);
views::Label* disclaimer = new views::Label(
avatar_menu_->GetSupervisedUserInformation());
disclaimer->SetMultiLine(true);
disclaimer->SetAllowCharacterBreak(true);
disclaimer->SetHorizontalAlignment(gfx::ALIGN_LEFT);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
disclaimer->SetFontList(rb->GetFontList(ui::ResourceBundle::SmallFont));
layout->StartRow(1, 0);
layout->AddView(disclaimer);
return view;
}
views::View* ProfileChooserView::CreateCurrentProfileAccountsView(
const AvatarMenu::Item& avatar_item) {
DCHECK(avatar_item.signed_in);
views::View* view = new views::View();
view->set_background(views::Background::CreateSolidBackground(
profiles::kAvatarBubbleAccountsBackgroundColor));
views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
Profile* profile = browser_->profile();
std::string primary_account =
SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedAccountId();
DCHECK(!primary_account.empty());
std::vector<std::string>accounts =
profiles::GetSecondaryAccountsForProfile(profile, primary_account);
// Get state of authentication error, if any.
std::string error_account_id = GetAuthErrorAccountId(profile);
// The primary account should always be listed first.
// TODO(rogerta): we still need to further differentiate the primary account
// from the others in the UI, so more work is likely required here:
// crbug.com/311124.
CreateAccountButton(layout, primary_account, true,
error_account_id == primary_account, kFixedMenuWidth);
for (size_t i = 0; i < accounts.size(); ++i)
CreateAccountButton(layout, accounts[i], false,
error_account_id == accounts[i], kFixedMenuWidth);
if (!profile->IsSupervised()) {
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
add_account_link_ = CreateLink(l10n_util::GetStringFUTF16(
IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, avatar_item.name), this);
add_account_link_->SetBorder(views::Border::CreateEmptyBorder(
0, views::kButtonVEdgeMarginNew,
views::kRelatedControlVerticalSpacing, 0));
layout->StartRow(1, 0);
layout->AddView(add_account_link_);
}
return view;
}
void ProfileChooserView::CreateAccountButton(views::GridLayout* layout,
const std::string& account_id,
bool is_primary_account,
bool reauth_required,
int width) {
std::string email = signin_ui_util::GetDisplayEmail(browser_->profile(),
account_id);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
const gfx::ImageSkia* delete_default_image =
rb->GetImageNamed(IDR_CLOSE_1).ToImageSkia();
const int kDeleteButtonWidth = delete_default_image->width();
const gfx::ImageSkia warning_default_image = reauth_required ?
*rb->GetImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia() :
gfx::ImageSkia();
const int kWarningButtonWidth = reauth_required ?
warning_default_image.width() + views::kRelatedButtonHSpacing : 0;
int available_width = width - 2 * views::kButtonHEdgeMarginNew
- kDeleteButtonWidth - kWarningButtonWidth;
views::LabelButton* email_button = new BackgroundColorHoverButton(
reauth_required ? this : NULL,
base::UTF8ToUTF16(email),
warning_default_image);
email_button->SetElideBehavior(gfx::ELIDE_EMAIL);
email_button->SetMinSize(gfx::Size(0, kButtonHeight));
email_button->SetMaxSize(gfx::Size(available_width, kButtonHeight));
layout->StartRow(1, 0);
layout->AddView(email_button);
if (reauth_required)
reauth_account_button_map_[email_button] = account_id;
// Delete button.
if (!browser_->profile()->IsSupervised()) {
views::ImageButton* delete_button = new views::ImageButton(this);
delete_button->SetImageAlignment(views::ImageButton::ALIGN_RIGHT,
views::ImageButton::ALIGN_MIDDLE);
delete_button->SetImage(views::ImageButton::STATE_NORMAL,
delete_default_image);
delete_button->SetImage(views::ImageButton::STATE_HOVERED,
rb->GetImageSkiaNamed(IDR_CLOSE_1_H));
delete_button->SetImage(views::ImageButton::STATE_PRESSED,
rb->GetImageSkiaNamed(IDR_CLOSE_1_P));
delete_button->SetBounds(
width - views::kButtonHEdgeMarginNew - kDeleteButtonWidth,
0, kDeleteButtonWidth, kButtonHeight);
email_button->set_notify_enter_exit_on_child(true);
email_button->AddChildView(delete_button);
// Save the original email address, as the button text could be elided.
delete_account_button_map_[delete_button] = account_id;
}
}
views::View* ProfileChooserView::CreateGaiaSigninView() {
GURL url;
int message_id;
switch (view_mode_) {
case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
url = signin::GetPromoURL(signin_metrics::SOURCE_AVATAR_BUBBLE_SIGN_IN,
false /* auto_close */,
true /* is_constrained */);
message_id = IDS_PROFILES_GAIA_SIGNIN_TITLE;
break;
case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
url = signin::GetPromoURL(
signin_metrics::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT,
false /* auto_close */,
true /* is_constrained */);
message_id = IDS_PROFILES_GAIA_ADD_ACCOUNT_TITLE;
break;
case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH: {
DCHECK(HasAuthError(browser_->profile()));
url = signin::GetReauthURL(browser_->profile(),
GetAuthErrorUsername(browser_->profile()));
message_id = IDS_PROFILES_GAIA_REAUTH_TITLE;
break;
}
default:
NOTREACHED() << "Called with invalid mode=" << view_mode_;
return NULL;
}
// Adds Gaia signin webview
Profile* profile = browser_->profile();
views::WebView* web_view = new views::WebView(profile);
web_view->LoadInitialURL(url);
web_view->GetWebContents()->SetDelegate(this);
web_view->SetPreferredSize(
gfx::Size(kFixedGaiaViewWidth, kFixedGaiaViewHeight));
content::RenderWidgetHostView* rwhv =
web_view->GetWebContents()->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetBackgroundColor(profiles::kAvatarBubbleGaiaBackgroundColor);
TitleCard* title_card = new TitleCard(l10n_util::GetStringUTF16(message_id),
this,
&gaia_signin_cancel_button_);
return TitleCard::AddPaddedTitleCard(
web_view, title_card, kFixedGaiaViewWidth);
}
views::View* ProfileChooserView::CreateAccountRemovalView() {
views::View* view = new views::View();
views::GridLayout* layout = CreateSingleColumnLayout(
view, kFixedAccountRemovalViewWidth - 2 * views::kButtonHEdgeMarginNew);
layout->SetInsets(0,
views::kButtonHEdgeMarginNew,
views::kButtonVEdgeMarginNew,
views::kButtonHEdgeMarginNew);
const std::string& primary_account = SigninManagerFactory::GetForProfile(
browser_->profile())->GetAuthenticatedAccountId();
bool is_primary_account = primary_account == account_id_to_remove_;
// Adds main text.
layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
const gfx::FontList& small_font_list =
rb->GetFontList(ui::ResourceBundle::SmallFont);
if (is_primary_account) {
std::string email = signin_ui_util::GetDisplayEmail(browser_->profile(),
account_id_to_remove_);
std::vector<size_t> offsets;
const base::string16 settings_text =
l10n_util::GetStringUTF16(IDS_PROFILES_SETTINGS_LINK);
const base::string16 primary_account_removal_text =
l10n_util::GetStringFUTF16(IDS_PROFILES_PRIMARY_ACCOUNT_REMOVAL_TEXT,
base::UTF8ToUTF16(email), settings_text, &offsets);
views::StyledLabel* primary_account_removal_label =
new views::StyledLabel(primary_account_removal_text, this);
primary_account_removal_label->AddStyleRange(
gfx::Range(offsets[1], offsets[1] + settings_text.size()),
views::StyledLabel::RangeStyleInfo::CreateForLink());
primary_account_removal_label->SetBaseFontList(small_font_list);
layout->AddView(primary_account_removal_label);
} else {
views::Label* content_label = new views::Label(
l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TEXT));
content_label->SetMultiLine(true);
content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
content_label->SetFontList(small_font_list);
layout->AddView(content_label);
}
// Adds button.
if (!is_primary_account) {
remove_account_button_ = new views::BlueButton(
this, l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_BUTTON));
remove_account_button_->SetHorizontalAlignment(
gfx::ALIGN_CENTER);
layout->StartRowWithPadding(
1, 0, 0, views::kUnrelatedControlVerticalSpacing);
layout->AddView(remove_account_button_);
} else {
layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
}
TitleCard* title_card = new TitleCard(
l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TITLE),
this, &account_removal_cancel_button_);
return TitleCard::AddPaddedTitleCard(view, title_card,
kFixedAccountRemovalViewWidth);
}
views::View* ProfileChooserView::CreateWelcomeUpgradeTutorialViewIfNeeded(
bool tutorial_shown, const AvatarMenu::Item& avatar_item) {
Profile* profile = browser_->profile();
const int show_count = profile->GetPrefs()->GetInteger(
prefs::kProfileAvatarTutorialShown);
// Do not show the tutorial if user has dismissed it.
if (show_count > signin_ui_util::kUpgradeWelcomeTutorialShowMax)
return NULL;
if (!tutorial_shown) {
if (show_count == signin_ui_util::kUpgradeWelcomeTutorialShowMax)
return NULL;
profile->GetPrefs()->SetInteger(
prefs::kProfileAvatarTutorialShown, show_count + 1);
}
ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_VIEW);
// For local profiles, the "Not you" link doesn't make sense.
base::string16 link_message = avatar_item.signed_in ?
l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatar_item.name) :
base::string16();
return CreateTutorialView(
profiles::TUTORIAL_MODE_WELCOME_UPGRADE,
l10n_util::GetStringUTF16(
IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE),
l10n_util::GetStringUTF16(
IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT),
link_message,
l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON),
true /* stack_button */,
&tutorial_not_you_link_,
&tutorial_see_whats_new_button_,
&tutorial_close_button_);
}
views::View* ProfileChooserView::CreateSigninConfirmationView() {
ProfileMetrics::LogProfileNewAvatarMenuSignin(
ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_VIEW);
return CreateTutorialView(
profiles::TUTORIAL_MODE_CONFIRM_SIGNIN,
l10n_util::GetStringUTF16(IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE),
l10n_util::GetStringUTF16(
IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT),
l10n_util::GetStringUTF16(IDS_PROFILES_SYNC_SETTINGS_LINK),
l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_OK_BUTTON),
false /* stack_button */,
&tutorial_sync_settings_link_,
&tutorial_sync_settings_ok_button_,
NULL /* close_button*/);
}
views::View* ProfileChooserView::CreateSigninErrorView() {
LoginUIService* login_ui_service =
LoginUIServiceFactory::GetForProfile(browser_->profile());
base::string16 last_login_result(login_ui_service->GetLastLoginResult());
return CreateTutorialView(
profiles::TUTORIAL_MODE_SHOW_ERROR,
l10n_util::GetStringUTF16(IDS_PROFILES_ERROR_TUTORIAL_TITLE),
last_login_result,
l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_TUTORIAL_LEARN_MORE),
base::string16(),
false /* stack_button */,
&tutorial_learn_more_link_,
NULL,
&tutorial_close_button_);
}
views::View* ProfileChooserView::CreateSwitchUserView() {
views::View* view = new views::View();
views::GridLayout* layout = CreateSingleColumnLayout(
view, kFixedSwitchUserViewWidth);
views::ColumnSet* columns = layout->AddColumnSet(1);
columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
int label_width =
kFixedSwitchUserViewWidth - 2 * views::kButtonHEdgeMarginNew;
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
views::GridLayout::FIXED, label_width, label_width);
columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
// Adds main text.
layout->StartRowWithPadding(1, 1, 0, views::kUnrelatedControlVerticalSpacing);
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
const gfx::FontList& small_font_list =
rb->GetFontList(ui::ResourceBundle::SmallFont);
const AvatarMenu::Item& avatar_item =
avatar_menu_->GetItemAt(avatar_menu_->GetActiveProfileIndex());
views::Label* content_label = new views::Label(
l10n_util::GetStringFUTF16(
IDS_PROFILES_NOT_YOU_CONTENT_TEXT, avatar_item.name));
content_label->SetMultiLine(true);
content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
content_label->SetFontList(small_font_list);
layout->AddView(content_label);
// Adds "Add person" button.
layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
add_person_button_ = new BackgroundColorHoverButton(
this,
l10n_util::GetStringUTF16(IDS_PROFILES_ADD_PERSON_BUTTON),
*rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
layout->StartRow(1, 0);
layout->AddView(add_person_button_);
// Adds "Disconnect your Google Account" button.
layout->StartRow(1, 0);
layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
disconnect_button_ = new BackgroundColorHoverButton(
this,
l10n_util::GetStringUTF16(IDS_PROFILES_DISCONNECT_BUTTON),
*rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_DISCONNECT));
layout->StartRow(1, 0);
layout->AddView(disconnect_button_);
TitleCard* title_card = new TitleCard(
l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatar_item.name),
this, &switch_user_cancel_button_);
return TitleCard::AddPaddedTitleCard(view, title_card,
kFixedSwitchUserViewWidth);
}
bool ProfileChooserView::ShouldShowGoIncognito() const {
bool incognito_available =
IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) !=
IncognitoModePrefs::DISABLED;
return incognito_available && !browser_->profile()->IsGuestSession();
}
void ProfileChooserView::PostActionPerformed(
ProfileMetrics::ProfileDesktopMenu action_performed) {
ProfileMetrics::LogProfileDesktopMenu(action_performed, gaia_service_type_);
gaia_service_type_ = signin::GAIA_SERVICE_TYPE_NONE;
}