1 // Copyright 2014 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.
5 #include "chrome/browser/ui/views/profiles/profile_chooser_view.h"
7 #include "base/prefs/pref_service.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/lifetime/application_lifetime.h"
11 #include "chrome/browser/prefs/incognito_mode_prefs.h"
12 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
13 #include "chrome/browser/profiles/profile_info_cache.h"
14 #include "chrome/browser/profiles/profile_manager.h"
15 #include "chrome/browser/profiles/profile_metrics.h"
16 #include "chrome/browser/profiles/profile_window.h"
17 #include "chrome/browser/profiles/profiles_state.h"
18 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
19 #include "chrome/browser/signin/signin_header_helper.h"
20 #include "chrome/browser/signin/signin_manager_factory.h"
21 #include "chrome/browser/signin/signin_promo.h"
22 #include "chrome/browser/signin/signin_ui_util.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/browser_commands.h"
25 #include "chrome/browser/ui/browser_dialogs.h"
26 #include "chrome/browser/ui/chrome_pages.h"
27 #include "chrome/browser/ui/singleton_tabs.h"
28 #include "chrome/browser/ui/views/profiles/user_manager_view.h"
29 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
30 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/common/url_constants.h"
33 #include "components/signin/core/browser/mutable_profile_oauth2_token_service.h"
34 #include "components/signin/core/browser/profile_oauth2_token_service.h"
35 #include "components/signin/core/browser/signin_error_controller.h"
36 #include "components/signin/core/browser/signin_manager.h"
37 #include "components/signin/core/common/profile_management_switches.h"
38 #include "grit/chromium_strings.h"
39 #include "grit/generated_resources.h"
40 #include "grit/theme_resources.h"
41 #include "third_party/skia/include/core/SkColor.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/base/resource/resource_bundle.h"
44 #include "ui/gfx/canvas.h"
45 #include "ui/gfx/image/image.h"
46 #include "ui/gfx/image/image_skia.h"
47 #include "ui/gfx/path.h"
48 #include "ui/gfx/skia_util.h"
49 #include "ui/gfx/text_elider.h"
50 #include "ui/native_theme/native_theme.h"
51 #include "ui/views/controls/button/blue_button.h"
52 #include "ui/views/controls/button/image_button.h"
53 #include "ui/views/controls/button/label_button.h"
54 #include "ui/views/controls/button/menu_button.h"
55 #include "ui/views/controls/label.h"
56 #include "ui/views/controls/link.h"
57 #include "ui/views/controls/separator.h"
58 #include "ui/views/controls/styled_label.h"
59 #include "ui/views/controls/textfield/textfield.h"
60 #include "ui/views/controls/webview/webview.h"
61 #include "ui/views/layout/grid_layout.h"
62 #include "ui/views/layout/layout_constants.h"
63 #include "ui/views/widget/widget.h"
67 // Helpers --------------------------------------------------------------------
69 const int kFixedMenuWidth = 250;
70 const int kButtonHeight = 32;
71 const int kFixedGaiaViewHeight = 440;
72 const int kFixedGaiaViewWidth = 360;
73 const int kFixedAccountRemovalViewWidth = 280;
74 const int kFixedSwitchUserViewWidth = 280;
75 const int kLargeImageSide = 88;
77 // Creates a GridLayout with a single column. This ensures that all the child
78 // views added get auto-expanded to fill the full width of the bubble.
79 views::GridLayout* CreateSingleColumnLayout(views::View* view, int width) {
80 views::GridLayout* layout = new views::GridLayout(view);
81 view->SetLayoutManager(layout);
83 views::ColumnSet* columns = layout->AddColumnSet(0);
84 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
85 views::GridLayout::FIXED, width, width);
89 views::Link* CreateLink(const base::string16& link_text,
90 views::LinkListener* listener) {
91 views::Link* link_button = new views::Link(link_text);
92 link_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
93 link_button->SetUnderline(false);
94 link_button->set_listener(listener);
98 gfx::ImageSkia CreateSquarePlaceholderImage(int size) {
100 bitmap.allocPixels(SkImageInfo::MakeA8(size, size));
101 bitmap.eraseARGB(0, 0, 0, 0);
102 return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
105 bool HasAuthError(Profile* profile) {
106 const SigninErrorController* error =
107 profiles::GetSigninErrorController(profile);
108 return error && error->HasError();
111 std::string GetAuthErrorAccountId(Profile* profile) {
112 const SigninErrorController* error =
113 profiles::GetSigninErrorController(profile);
115 return std::string();
117 return error->error_account_id();
120 std::string GetAuthErrorUsername(Profile* profile) {
121 const SigninErrorController* error =
122 profiles::GetSigninErrorController(profile);
124 return std::string();
126 return error->error_username();
129 // BackgroundColorHoverButton -------------------------------------------------
131 // A custom button that allows for setting a background color when hovered over.
132 class BackgroundColorHoverButton : public views::LabelButton {
134 BackgroundColorHoverButton(views::ButtonListener* listener,
135 const base::string16& text,
136 const gfx::ImageSkia& icon)
137 : views::LabelButton(listener, text) {
138 SetImageLabelSpacing(views::kItemLabelSpacing);
139 SetBorder(views::Border::CreateEmptyBorder(
140 0, views::kButtonHEdgeMarginNew, 0, views::kButtonHEdgeMarginNew));
141 SetMinSize(gfx::Size(0,
142 kButtonHeight + views::kRelatedControlVerticalSpacing));
143 SetImage(STATE_NORMAL, icon);
147 virtual ~BackgroundColorHoverButton() {}
150 // views::LabelButton:
151 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
152 if ((state() == STATE_PRESSED) ||
153 (state() == STATE_HOVERED) ||
155 canvas->DrawColor(GetNativeTheme()->GetSystemColor(
156 ui::NativeTheme::kColorId_ButtonHoverBackgroundColor));
158 LabelButton::OnPaint(canvas);
161 DISALLOW_COPY_AND_ASSIGN(BackgroundColorHoverButton);
164 // SizedContainer -------------------------------------------------
166 // A simple container view that takes an explicit preferred size.
167 class SizedContainer : public views::View {
169 explicit SizedContainer(const gfx::Size& preferred_size)
170 : preferred_size_(preferred_size) {}
172 virtual gfx::Size GetPreferredSize() const OVERRIDE {
173 return preferred_size_;
177 gfx::Size preferred_size_;
182 // RightAlignedIconLabelButton -------------------------------------------------
184 // A custom LabelButton that has a centered text and right aligned icon.
185 class RightAlignedIconLabelButton : public views::LabelButton {
187 RightAlignedIconLabelButton(views::ButtonListener* listener,
188 const base::string16& text)
189 : views::LabelButton(listener, text) {
193 virtual void Layout() OVERRIDE {
194 // This layout trick keeps the text left-aligned and the icon right-aligned.
195 SetHorizontalAlignment(gfx::ALIGN_RIGHT);
196 views::LabelButton::Layout();
197 label()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
200 DISALLOW_COPY_AND_ASSIGN(RightAlignedIconLabelButton);
203 // EditableProfilePhoto -------------------------------------------------
205 // A custom Image control that shows a "change" button when moused over.
206 class EditableProfilePhoto : public views::LabelButton {
208 EditableProfilePhoto(views::ButtonListener* listener,
209 const gfx::Image& icon,
210 bool is_editing_allowed,
211 const gfx::Rect& bounds)
212 : views::LabelButton(listener, base::string16()),
213 photo_overlay_(NULL) {
214 gfx::Image image = profiles::GetSizedAvatarIcon(
215 icon, true, kLargeImageSide, kLargeImageSide);
216 SetImage(views::LabelButton::STATE_NORMAL, *image.ToImageSkia());
217 SetBorder(views::Border::NullBorder());
218 SetBoundsRect(bounds);
220 // Calculate the circular mask that will be used to display the photo.
221 circular_mask_.addCircle(SkIntToScalar(bounds.width() / 2),
222 SkIntToScalar(bounds.height() / 2),
223 SkIntToScalar(bounds.width() / 2));
225 if (!is_editing_allowed) {
231 set_notify_enter_exit_on_child(true);
233 // Photo overlay that appears when hovering over the button.
234 photo_overlay_ = new views::ImageView();
236 const SkColor kBackgroundColor = SkColorSetARGB(65, 255, 255, 255);
237 photo_overlay_->set_background(
238 views::Background::CreateSolidBackground(kBackgroundColor));
239 photo_overlay_->SetImage(*ui::ResourceBundle::GetSharedInstance().
240 GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_CAMERA));
242 photo_overlay_->SetSize(bounds.size());
243 photo_overlay_->SetVisible(false);
244 AddChildView(photo_overlay_);
247 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
248 // Display the profile picture as a circle.
249 canvas->ClipPath(circular_mask_, true);
250 views::LabelButton::OnPaint(canvas);
253 virtual void PaintChildren(gfx::Canvas* canvas,
254 const views::CullSet& cull_set) OVERRIDE {
255 // Display any children (the "change photo" overlay) as a circle.
256 canvas->ClipPath(circular_mask_, true);
257 View::PaintChildren(canvas, cull_set);
261 // views::CustomButton:
262 virtual void StateChanged() OVERRIDE {
264 (state() == STATE_PRESSED || state() == STATE_HOVERED || HasFocus());
266 photo_overlay_->SetVisible(show_overlay);
269 virtual void OnFocus() OVERRIDE {
270 views::LabelButton::OnFocus();
272 photo_overlay_->SetVisible(true);
275 virtual void OnBlur() OVERRIDE {
276 views::LabelButton::OnBlur();
277 // Don't hide the overlay if it's being shown as a result of a mouseover.
278 if (photo_overlay_ && state() != STATE_HOVERED)
279 photo_overlay_->SetVisible(false);
282 gfx::Path circular_mask_;
284 // Image that is shown when hovering over the image button. Can be NULL if
285 // the photo isn't allowed to be edited (e.g. for guest profiles).
286 views::ImageView* photo_overlay_;
288 DISALLOW_COPY_AND_ASSIGN(EditableProfilePhoto);
291 // EditableProfileName -------------------------------------------------
293 // A custom text control that turns into a textfield for editing when clicked.
294 class EditableProfileName : public RightAlignedIconLabelButton,
295 public views::ButtonListener {
297 EditableProfileName(views::TextfieldController* controller,
298 const base::string16& text,
299 bool is_editing_allowed)
300 : RightAlignedIconLabelButton(this, text),
301 profile_name_textfield_(NULL) {
302 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
303 const gfx::FontList& medium_font_list =
304 rb->GetFontList(ui::ResourceBundle::MediumFont);
305 SetFontList(medium_font_list);
306 SetHorizontalAlignment(gfx::ALIGN_CENTER);
308 if (!is_editing_allowed) {
309 SetBorder(views::Border::CreateEmptyBorder(2, 0, 2, 0));
314 // Show an "edit" pencil icon when hovering over. In the default state,
315 // we need to create an empty placeholder of the correct size, so that
316 // the text doesn't jump around when the hovered icon appears.
317 gfx::ImageSkia hover_image =
318 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_HOVER);
319 SetImage(STATE_NORMAL, CreateSquarePlaceholderImage(hover_image.width()));
320 SetImage(STATE_HOVERED, hover_image);
321 SetImage(STATE_PRESSED,
322 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_PRESSED));
323 // To center the text, we need to offest it by the width of the icon we
324 // are adding and its padding. We need to also add a small top/bottom
325 // padding to account for the textfield's border.
326 const int kIconTextLabelButtonSpacing = 5;
327 SetBorder(views::Border::CreateEmptyBorder(
328 2, hover_image.width() + kIconTextLabelButtonSpacing, 2, 0));
330 // Textfield that overlaps the button.
331 profile_name_textfield_ = new views::Textfield();
332 profile_name_textfield_->set_controller(controller);
333 profile_name_textfield_->SetFontList(medium_font_list);
334 profile_name_textfield_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
336 profile_name_textfield_->SetVisible(false);
337 AddChildView(profile_name_textfield_);
340 views::Textfield* profile_name_textfield() {
341 return profile_name_textfield_;
344 // Hide the editable textfield to show the profile name button instead.
345 void ShowReadOnlyView() {
346 if (profile_name_textfield_)
347 profile_name_textfield_->SetVisible(false);
351 // views::ButtonListener:
352 virtual void ButtonPressed(views::Button* sender,
353 const ui::Event& event) OVERRIDE {
354 if (profile_name_textfield_) {
355 profile_name_textfield_->SetVisible(true);
356 profile_name_textfield_->SetText(GetText());
357 profile_name_textfield_->SelectAll(false);
358 profile_name_textfield_->RequestFocus();
362 // views::LabelButton:
363 virtual bool OnKeyReleased(const ui::KeyEvent& event) OVERRIDE {
364 // Override CustomButton's implementation, which presses the button when
365 // you press space and clicks it when you release space, as the space can be
366 // part of the new profile name typed in the textfield.
370 virtual void Layout() OVERRIDE {
371 if (profile_name_textfield_)
372 profile_name_textfield_->SetBounds(0, 0, width(), height());
373 RightAlignedIconLabelButton::Layout();
376 virtual void OnFocus() OVERRIDE {
377 RightAlignedIconLabelButton::OnFocus();
378 SetState(STATE_HOVERED);
381 virtual void OnBlur() OVERRIDE {
382 RightAlignedIconLabelButton::OnBlur();
383 SetState(STATE_NORMAL);
386 // Textfield that is shown when editing the profile name. Can be NULL if
387 // the profile name isn't allowed to be edited (e.g. for guest profiles).
388 views::Textfield* profile_name_textfield_;
390 DISALLOW_COPY_AND_ASSIGN(EditableProfileName);
393 // A title card with one back button right aligned and one label center aligned.
394 class TitleCard : public views::View {
396 TitleCard(const base::string16& message, views::ButtonListener* listener,
397 views::ImageButton** back_button) {
398 back_button_ = new views::ImageButton(listener);
399 back_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
400 views::ImageButton::ALIGN_MIDDLE);
401 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
402 back_button_->SetImage(views::ImageButton::STATE_NORMAL,
403 rb->GetImageSkiaNamed(IDR_BACK));
404 back_button_->SetImage(views::ImageButton::STATE_HOVERED,
405 rb->GetImageSkiaNamed(IDR_BACK_H));
406 back_button_->SetImage(views::ImageButton::STATE_PRESSED,
407 rb->GetImageSkiaNamed(IDR_BACK_P));
408 back_button_->SetImage(views::ImageButton::STATE_DISABLED,
409 rb->GetImageSkiaNamed(IDR_BACK_D));
410 back_button_->SetFocusable(true);
411 *back_button = back_button_;
413 title_label_ = new views::Label(message);
414 title_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
415 const gfx::FontList& medium_font_list =
416 rb->GetFontList(ui::ResourceBundle::MediumFont);
417 title_label_->SetFontList(medium_font_list);
419 AddChildView(back_button_);
420 AddChildView(title_label_);
423 // Creates a new view that has the |title_card| with padding at the top, an
424 // edge-to-edge separator below, and the specified |view| at the bottom.
425 static views::View* AddPaddedTitleCard(views::View* view,
426 TitleCard* title_card,
428 views::View* titled_view = new views::View();
429 views::GridLayout* layout = new views::GridLayout(titled_view);
430 titled_view->SetLayoutManager(layout);
432 // Column set 0 is a single column layout with horizontal padding at left
433 // and right, and column set 1 is a single column layout with no padding.
434 views::ColumnSet* columns = layout->AddColumnSet(0);
435 columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew);
436 int available_width = width - 2 * views::kButtonHEdgeMarginNew;
437 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
438 views::GridLayout::FIXED, available_width, available_width);
439 columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew);
440 layout->AddColumnSet(1)->AddColumn(views::GridLayout::FILL,
441 views::GridLayout::FILL, 0,views::GridLayout::FIXED, width, width);
443 layout->StartRowWithPadding(1, 0, 0, views::kButtonVEdgeMarginNew);
444 layout->AddView(title_card);
445 layout->StartRowWithPadding(1, 1, 0, views::kRelatedControlVerticalSpacing);
446 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
448 layout->StartRow(1, 1);
449 layout->AddView(view);
455 virtual void Layout() OVERRIDE{
456 int back_button_width = back_button_->GetPreferredSize().width();
457 back_button_->SetBounds(0, 0, back_button_width, height());
458 int label_padding = back_button_width + views::kButtonHEdgeMarginNew;
459 int label_width = width() - 2 * label_padding;
460 DCHECK_GT(label_width, 0);
461 title_label_->SetBounds(label_padding, 0, label_width, height());
464 virtual gfx::Size GetPreferredSize() const OVERRIDE{
465 int height = std::max(title_label_->GetPreferredSize().height(),
466 back_button_->GetPreferredSize().height());
467 return gfx::Size(width(), height);
470 views::ImageButton* back_button_;
471 views::Label* title_label_;
473 DISALLOW_COPY_AND_ASSIGN(TitleCard);
476 // ProfileChooserView ---------------------------------------------------------
479 ProfileChooserView* ProfileChooserView::profile_bubble_ = NULL;
480 bool ProfileChooserView::close_on_deactivate_for_testing_ = true;
483 void ProfileChooserView::ShowBubble(
484 profiles::BubbleViewMode view_mode,
485 profiles::TutorialMode tutorial_mode,
486 const signin::ManageAccountsParams& manage_accounts_params,
487 views::View* anchor_view,
488 views::BubbleBorder::Arrow arrow,
489 views::BubbleBorder::BubbleAlignment border_alignment,
492 if (tutorial_mode != profiles::TUTORIAL_MODE_NONE) {
493 profile_bubble_->tutorial_mode_ = tutorial_mode;
494 profile_bubble_->ShowView(view_mode, profile_bubble_->avatar_menu_.get());
499 profile_bubble_ = new ProfileChooserView(anchor_view, arrow, browser,
500 view_mode, tutorial_mode, manage_accounts_params.service_type);
501 views::BubbleDelegateView::CreateBubble(profile_bubble_);
502 profile_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_);
503 profile_bubble_->SetAlignment(border_alignment);
504 profile_bubble_->GetWidget()->Show();
505 profile_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
509 bool ProfileChooserView::IsShowing() {
510 return profile_bubble_ != NULL;
514 void ProfileChooserView::Hide() {
516 profile_bubble_->GetWidget()->Close();
519 ProfileChooserView::ProfileChooserView(views::View* anchor_view,
520 views::BubbleBorder::Arrow arrow,
522 profiles::BubbleViewMode view_mode,
523 profiles::TutorialMode tutorial_mode,
524 signin::GAIAServiceType service_type)
525 : BubbleDelegateView(anchor_view, arrow),
527 view_mode_(view_mode),
528 tutorial_mode_(tutorial_mode),
529 gaia_service_type_(service_type) {
530 // Reset the default margins inherited from the BubbleDelegateView.
531 // Add a small bottom inset so that the bubble's rounded corners show up.
532 set_margins(gfx::Insets(0, 0, 1, 0));
533 set_background(views::Background::CreateSolidBackground(
534 GetNativeTheme()->GetSystemColor(
535 ui::NativeTheme::kColorId_DialogBackground)));
538 avatar_menu_.reset(new AvatarMenu(
539 &g_browser_process->profile_manager()->GetProfileInfoCache(),
542 avatar_menu_->RebuildMenu();
544 ProfileOAuth2TokenService* oauth2_token_service =
545 ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
546 if (oauth2_token_service)
547 oauth2_token_service->AddObserver(this);
550 ProfileChooserView::~ProfileChooserView() {
551 ProfileOAuth2TokenService* oauth2_token_service =
552 ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
553 if (oauth2_token_service)
554 oauth2_token_service->RemoveObserver(this);
557 void ProfileChooserView::ResetView() {
558 open_other_profile_indexes_map_.clear();
559 delete_account_button_map_.clear();
560 reauth_account_button_map_.clear();
561 manage_accounts_link_ = NULL;
562 signin_current_profile_link_ = NULL;
563 auth_error_email_button_ = NULL;
564 current_profile_photo_ = NULL;
565 current_profile_name_ = NULL;
566 users_button_ = NULL;
567 go_incognito_button_ = NULL;
569 add_account_link_ = NULL;
570 gaia_signin_cancel_button_ = NULL;
571 remove_account_button_ = NULL;
572 account_removal_cancel_button_ = NULL;
573 add_person_button_ = NULL;
574 disconnect_button_ = NULL;
575 switch_user_cancel_button_ = NULL;
576 tutorial_sync_settings_ok_button_ = NULL;
577 tutorial_close_button_ = NULL;
578 tutorial_sync_settings_link_ = NULL;
579 tutorial_see_whats_new_button_ = NULL;
580 tutorial_not_you_link_ = NULL;
581 tutorial_learn_more_link_ = NULL;
584 void ProfileChooserView::Init() {
585 // If view mode is PROFILE_CHOOSER but there is an auth error, force
586 // ACCOUNT_MANAGEMENT mode.
587 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER &&
588 HasAuthError(browser_->profile()) &&
589 switches::IsEnableAccountConsistency() &&
590 avatar_menu_->GetItemAt(avatar_menu_->GetActiveProfileIndex()).
592 view_mode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT;
595 ShowView(view_mode_, avatar_menu_.get());
598 void ProfileChooserView::OnAvatarMenuChanged(
599 AvatarMenu* avatar_menu) {
600 // Do not refresh the avatar menu if the user is on a signin related view.
601 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN ||
602 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
603 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
607 // Refresh the view with the new menu. We can't just update the local copy
608 // as this may have been triggered by a sign out action, in which case
609 // the view is being destroyed.
610 ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu);
613 void ProfileChooserView::OnRefreshTokenAvailable(
614 const std::string& account_id) {
615 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ||
616 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
617 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
618 // The account management UI is only available through the
619 // --enable-account-consistency flag.
620 ShowView(switches::IsEnableAccountConsistency() ?
621 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
622 profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
626 void ProfileChooserView::OnRefreshTokenRevoked(const std::string& account_id) {
627 // Refresh the account management view when an account is removed from the
629 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT)
630 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
633 void ProfileChooserView::ShowView(profiles::BubbleViewMode view_to_display,
634 AvatarMenu* avatar_menu) {
635 // The account management view should only be displayed if the active profile
637 if (view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) {
638 DCHECK(switches::IsEnableAccountConsistency());
639 const AvatarMenu::Item& active_item = avatar_menu->GetItemAt(
640 avatar_menu->GetActiveProfileIndex());
641 DCHECK(active_item.signed_in);
644 if (browser_->profile()->IsSupervised() &&
645 (view_to_display == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
646 view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL)) {
647 LOG(WARNING) << "Supervised user attempted to add/remove account";
652 RemoveAllChildViews(true);
653 view_mode_ = view_to_display;
655 views::GridLayout* layout;
656 views::View* sub_view;
657 switch (view_mode_) {
658 case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
659 case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
660 case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH:
661 layout = CreateSingleColumnLayout(this, kFixedGaiaViewWidth);
662 sub_view = CreateGaiaSigninView();
664 case profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL:
665 layout = CreateSingleColumnLayout(this, kFixedAccountRemovalViewWidth);
666 sub_view = CreateAccountRemovalView();
668 case profiles::BUBBLE_VIEW_MODE_SWITCH_USER:
669 layout = CreateSingleColumnLayout(this, kFixedSwitchUserViewWidth);
670 sub_view = CreateSwitchUserView();
671 ProfileMetrics::LogProfileNewAvatarMenuNotYou(
672 ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_VIEW);
675 layout = CreateSingleColumnLayout(this, kFixedMenuWidth);
676 sub_view = CreateProfileChooserView(avatar_menu);
678 // Clears tutorial mode for all non-profile-chooser views.
679 if (view_mode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER)
680 tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
682 layout->StartRow(1, 0);
683 layout->AddView(sub_view);
685 if (GetBubbleFrameView())
689 void ProfileChooserView::WindowClosing() {
690 DCHECK_EQ(profile_bubble_, this);
691 profile_bubble_ = NULL;
693 if (tutorial_mode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
694 LoginUIServiceFactory::GetForProfile(browser_->profile())->
695 SyncConfirmationUIClosed(false /* configure_sync_first */);
699 void ProfileChooserView::ButtonPressed(views::Button* sender,
700 const ui::Event& event) {
701 // Disable button after clicking so that it doesn't get clicked twice and
702 // start a second action... which can crash Chrome. But don't disable if it
703 // has no parent (like in tests) because that will also crash.
704 if (sender->parent())
705 sender->SetEnabled(false);
707 if (sender == users_button_) {
708 // If this is a guest session, also close all the guest browser windows.
709 if (browser_->profile()->IsGuestSession()) {
710 chrome::ShowUserManager(base::FilePath());
711 profiles::CloseGuestProfileWindows();
713 chrome::ShowUserManager(browser_->profile()->GetPath());
715 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_OPEN_USER_MANAGER);
716 } else if (sender == go_incognito_button_) {
717 DCHECK(ShouldShowGoIncognito());
718 chrome::NewIncognitoWindow(browser_);
719 } else if (sender == lock_button_) {
720 profiles::LockProfile(browser_->profile());
721 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_LOCK);
722 } else if (sender == auth_error_email_button_) {
723 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get());
724 } else if (sender == tutorial_sync_settings_ok_button_) {
725 LoginUIServiceFactory::GetForProfile(browser_->profile())->
726 SyncConfirmationUIClosed(false /* configure_sync_first */);
728 ProfileMetrics::LogProfileNewAvatarMenuSignin(
729 ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_OK);
730 } else if (sender == tutorial_close_button_) {
731 DCHECK(tutorial_mode_ != profiles::TUTORIAL_MODE_NONE &&
732 tutorial_mode_ != profiles::TUTORIAL_MODE_CONFIRM_SIGNIN);
734 } else if (sender == tutorial_see_whats_new_button_) {
735 ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
736 ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_WHATS_NEW);
737 chrome::ShowUserManagerWithTutorial(
738 profiles::USER_MANAGER_TUTORIAL_OVERVIEW);
739 } else if (sender == remove_account_button_) {
741 } else if (sender == account_removal_cancel_button_) {
742 account_id_to_remove_.clear();
743 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
744 } else if (sender == gaia_signin_cancel_button_) {
745 std::string primary_account =
746 SigninManagerFactory::GetForProfile(browser_->profile())->
747 GetAuthenticatedUsername();
748 // The account management view is only available with the
749 // --enable-account-consistency flag.
750 bool account_management_available = !primary_account.empty() &&
751 switches::IsEnableAccountConsistency();
752 ShowView(account_management_available ?
753 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
754 profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
755 } else if (sender == current_profile_photo_) {
756 avatar_menu_->EditProfile(avatar_menu_->GetActiveProfileIndex());
757 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE);
758 } else if (sender == signin_current_profile_link_) {
759 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN, avatar_menu_.get());
760 } else if (sender == add_person_button_) {
761 ProfileMetrics::LogProfileNewAvatarMenuNotYou(
762 ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_ADD_PERSON);
763 chrome::ShowUserManager(browser_->profile()->GetPath());
764 } else if (sender == disconnect_button_) {
765 ProfileMetrics::LogProfileNewAvatarMenuNotYou(
766 ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_DISCONNECT);
767 chrome::ShowSettings(browser_);
768 } else if (sender == switch_user_cancel_button_) {
769 ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
770 ProfileMetrics::LogProfileNewAvatarMenuNotYou(
771 ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_BACK);
773 // Either one of the "other profiles", or one of the profile accounts
774 // buttons was pressed.
775 ButtonIndexes::const_iterator profile_match =
776 open_other_profile_indexes_map_.find(sender);
777 if (profile_match != open_other_profile_indexes_map_.end()) {
778 avatar_menu_->SwitchToProfile(
779 profile_match->second,
780 ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW,
781 ProfileMetrics::SWITCH_PROFILE_ICON);
783 // This was a profile accounts button.
784 AccountButtonIndexes::const_iterator account_match =
785 delete_account_button_map_.find(sender);
786 if (account_match != delete_account_button_map_.end()) {
787 account_id_to_remove_ = account_match->second;
788 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL,
791 account_match = reauth_account_button_map_.find(sender);
792 DCHECK(account_match != reauth_account_button_map_.end());
793 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get());
799 void ProfileChooserView::RemoveAccount() {
800 DCHECK(!account_id_to_remove_.empty());
801 MutableProfileOAuth2TokenService* oauth2_token_service =
802 ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(
803 browser_->profile());
804 if (oauth2_token_service) {
805 oauth2_token_service->RevokeCredentials(account_id_to_remove_);
806 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_REMOVE_ACCT);
808 account_id_to_remove_.clear();
810 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
813 void ProfileChooserView::LinkClicked(views::Link* sender, int event_flags) {
814 if (sender == manage_accounts_link_) {
815 // This link can either mean show/hide the account management view,
816 // depending on which view it is displayed. ShowView() will DCHECK if
817 // the account management view is displayed for non signed-in users.
819 view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ?
820 profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER :
821 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT,
823 } else if (sender == add_account_link_) {
824 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT, avatar_menu_.get());
825 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT);
826 } else if (sender == tutorial_sync_settings_link_) {
827 LoginUIServiceFactory::GetForProfile(browser_->profile())->
828 SyncConfirmationUIClosed(true /* configure_sync_first */);
829 tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
830 ProfileMetrics::LogProfileNewAvatarMenuSignin(
831 ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_SETTINGS);
832 } else if (sender == tutorial_not_you_link_){
833 ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
834 ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_NOT_YOU);
835 ShowView(profiles::BUBBLE_VIEW_MODE_SWITCH_USER, avatar_menu_.get());
837 DCHECK(sender == tutorial_learn_more_link_);
838 signin_ui_util::ShowSigninErrorLearnMorePage(browser_->profile());
842 void ProfileChooserView::StyledLabelLinkClicked(
843 const gfx::Range& range, int event_flags) {
844 chrome::ShowSettings(browser_);
847 bool ProfileChooserView::HandleKeyEvent(views::Textfield* sender,
848 const ui::KeyEvent& key_event) {
849 views::Textfield* name_textfield =
850 current_profile_name_->profile_name_textfield();
851 DCHECK(sender == name_textfield);
853 if (key_event.key_code() == ui::VKEY_RETURN ||
854 key_event.key_code() == ui::VKEY_TAB) {
855 // Pressing Tab/Enter commits the new profile name, unless it's empty.
856 base::string16 new_profile_name = name_textfield->text();
857 base::TrimWhitespace(new_profile_name, base::TRIM_ALL, &new_profile_name);
858 if (new_profile_name.empty())
861 const AvatarMenu::Item& active_item = avatar_menu_->GetItemAt(
862 avatar_menu_->GetActiveProfileIndex());
863 Profile* profile = g_browser_process->profile_manager()->GetProfile(
864 active_item.profile_path);
867 if (profile->IsSupervised())
870 profiles::UpdateProfileName(profile, new_profile_name);
871 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME);
872 current_profile_name_->ShowReadOnlyView();
878 views::View* ProfileChooserView::CreateProfileChooserView(
879 AvatarMenu* avatar_menu) {
880 views::View* view = new views::View();
881 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
882 // Separate items into active and alternatives.
883 Indexes other_profiles;
884 views::View* tutorial_view = NULL;
885 views::View* current_profile_view = NULL;
886 views::View* current_profile_accounts = NULL;
887 views::View* option_buttons_view = NULL;
888 for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) {
889 const AvatarMenu::Item& item = avatar_menu->GetItemAt(i);
891 option_buttons_view = CreateOptionsView(
892 switches::IsNewProfileManagement() && item.signed_in);
893 current_profile_view = CreateCurrentProfileView(item, false);
894 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
895 switch (tutorial_mode_) {
896 case profiles::TUTORIAL_MODE_NONE:
897 case profiles::TUTORIAL_MODE_WELCOME_UPGRADE:
898 tutorial_view = CreateWelcomeUpgradeTutorialViewIfNeeded(
899 tutorial_mode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE,
902 case profiles::TUTORIAL_MODE_CONFIRM_SIGNIN:
903 tutorial_view = CreateSigninConfirmationView();
905 case profiles::TUTORIAL_MODE_SHOW_ERROR:
906 tutorial_view = CreateSigninErrorView();
910 current_profile_accounts = CreateCurrentProfileAccountsView(item);
913 other_profiles.push_back(i);
918 // TODO(mlerman): update UMA stats for the new tutorial.
919 layout->StartRow(1, 0);
920 layout->AddView(tutorial_view);
922 tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
925 if (!current_profile_view) {
926 // Guest windows don't have an active profile.
927 current_profile_view = CreateGuestProfileView();
928 option_buttons_view = CreateOptionsView(false);
931 layout->StartRow(1, 0);
932 layout->AddView(current_profile_view);
934 if (view_mode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
935 DCHECK(current_profile_accounts);
936 layout->StartRow(0, 0);
937 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
938 layout->StartRow(1, 0);
939 layout->AddView(current_profile_accounts);
942 if (browser_->profile()->IsSupervised()) {
943 layout->StartRow(0, 0);
944 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
945 layout->StartRow(1, 0);
946 layout->AddView(CreateSupervisedUserDisclaimerView());
949 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
950 layout->StartRow(1, 0);
951 if (switches::IsFastUserSwitching())
952 layout->AddView(CreateOtherProfilesView(other_profiles));
955 layout->StartRow(0, 0);
956 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
958 if (option_buttons_view) {
959 layout->StartRow(0, 0);
960 layout->AddView(option_buttons_view);
966 void ProfileChooserView::DismissTutorial() {
967 // Never shows the upgrade tutorial again if manually closed.
968 if (tutorial_mode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
969 browser_->profile()->GetPrefs()->SetInteger(
970 prefs::kProfileAvatarTutorialShown,
971 signin_ui_util::kUpgradeWelcomeTutorialShowMax + 1);
974 tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
975 ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
978 views::View* ProfileChooserView::CreateTutorialView(
979 profiles::TutorialMode tutorial_mode,
980 const base::string16& title_text,
981 const base::string16& content_text,
982 const base::string16& link_text,
983 const base::string16& button_text,
986 views::LabelButton** button,
987 views::ImageButton** close_button) {
988 tutorial_mode_ = tutorial_mode;
990 views::View* view = new views::View();
991 view->set_background(views::Background::CreateSolidBackground(
992 profiles::kAvatarTutorialBackgroundColor));
993 views::GridLayout* layout = CreateSingleColumnLayout(view,
994 kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew);
995 // Creates a second column set for buttons and links.
996 views::ColumnSet* button_columns = layout->AddColumnSet(1);
997 button_columns->AddColumn(views::GridLayout::LEADING,
998 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
999 button_columns->AddPaddingColumn(
1000 1, views::kUnrelatedControlHorizontalSpacing);
1001 button_columns->AddColumn(views::GridLayout::TRAILING,
1002 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
1003 layout->SetInsets(views::kButtonVEdgeMarginNew,
1004 views::kButtonHEdgeMarginNew,
1005 views::kButtonVEdgeMarginNew,
1006 views::kButtonHEdgeMarginNew);
1008 // Adds title and close button if needed.
1009 views::Label* title_label = new views::Label(title_text);
1010 title_label->SetMultiLine(true);
1011 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1012 title_label->SetAutoColorReadabilityEnabled(false);
1013 title_label->SetEnabledColor(SK_ColorWHITE);
1014 title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
1015 ui::ResourceBundle::MediumFont));
1018 layout->StartRow(1, 1);
1019 layout->AddView(title_label);
1020 *close_button = new views::ImageButton(this);
1021 (*close_button)->SetImageAlignment(views::ImageButton::ALIGN_RIGHT,
1022 views::ImageButton::ALIGN_MIDDLE);
1023 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1024 (*close_button)->SetImage(views::ImageButton::STATE_NORMAL,
1025 rb->GetImageSkiaNamed(IDR_CLOSE_1));
1026 (*close_button)->SetImage(views::ImageButton::STATE_HOVERED,
1027 rb->GetImageSkiaNamed(IDR_CLOSE_1_H));
1028 (*close_button)->SetImage(views::ImageButton::STATE_PRESSED,
1029 rb->GetImageSkiaNamed(IDR_CLOSE_1_P));
1030 layout->AddView(*close_button);
1032 layout->StartRow(1, 0);
1033 layout->AddView(title_label);
1036 // Adds body content.
1037 views::Label* content_label = new views::Label(content_text);
1038 content_label->SetMultiLine(true);
1039 content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1040 content_label->SetAutoColorReadabilityEnabled(false);
1041 content_label->SetEnabledColor(profiles::kAvatarTutorialContentTextColor);
1042 layout->StartRowWithPadding(1, 0, 0, views::kRelatedControlVerticalSpacing);
1043 layout->AddView(content_label);
1045 // Adds links and buttons.
1046 bool has_button = !button_text.empty();
1048 *button = new views::LabelButton(this, button_text);
1049 (*button)->SetHorizontalAlignment(gfx::ALIGN_CENTER);
1050 (*button)->SetStyle(views::Button::STYLE_BUTTON);
1053 bool has_link = !link_text.empty();
1055 *link = CreateLink(link_text, this);
1056 (*link)->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1057 (*link)->SetAutoColorReadabilityEnabled(false);
1058 (*link)->SetEnabledColor(SK_ColorWHITE);
1063 layout->StartRowWithPadding(
1064 1, 0, 0, views::kUnrelatedControlVerticalSpacing);
1065 layout->AddView(*button);
1067 layout->StartRowWithPadding(
1068 1, 0, 0, views::kRelatedControlVerticalSpacing);
1069 (*link)->SetHorizontalAlignment(gfx::ALIGN_CENTER);
1070 layout->AddView(*link);
1073 DCHECK(has_link || has_button);
1074 layout->StartRowWithPadding(
1075 1, 1, 0, views::kUnrelatedControlVerticalSpacing);
1077 layout->AddView(*link);
1079 layout->SkipColumns(1);
1081 layout->AddView(*button);
1083 layout->SkipColumns(1);
1089 views::View* ProfileChooserView::CreateCurrentProfileView(
1090 const AvatarMenu::Item& avatar_item,
1092 views::View* view = new views::View();
1093 int column_width = kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew;
1094 views::GridLayout* layout = CreateSingleColumnLayout(view, column_width);
1095 layout->SetInsets(views::kButtonVEdgeMarginNew,
1096 views::kButtonHEdgeMarginNew,
1097 views::kUnrelatedControlVerticalSpacing,
1098 views::kButtonHEdgeMarginNew);
1100 // Profile icon, centered.
1101 int x_offset = (column_width - kLargeImageSide) / 2;
1102 current_profile_photo_ = new EditableProfilePhoto(
1103 this, avatar_item.icon, !is_guest,
1104 gfx::Rect(x_offset, 0, kLargeImageSide, kLargeImageSide));
1105 SizedContainer* profile_icon_container =
1106 new SizedContainer(gfx::Size(column_width, kLargeImageSide));
1107 profile_icon_container->AddChildView(current_profile_photo_);
1109 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1110 if (browser_->profile()->IsSupervised()) {
1111 views::ImageView* supervised_icon = new views::ImageView();
1112 supervised_icon->SetImage(
1113 rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_SUPERVISED));
1114 gfx::Size preferred_size = supervised_icon->GetPreferredSize();
1115 gfx::Rect parent_bounds = current_profile_photo_->bounds();
1116 supervised_icon->SetBounds(
1117 parent_bounds.right() - preferred_size.width(),
1118 parent_bounds.bottom() - preferred_size.height(),
1119 preferred_size.width(),
1120 preferred_size.height());
1121 profile_icon_container->AddChildView(supervised_icon);
1124 layout->StartRow(1, 0);
1125 layout->AddView(profile_icon_container);
1127 // Profile name, centered.
1128 bool editing_allowed = !is_guest && !browser_->profile()->IsSupervised();
1129 current_profile_name_ = new EditableProfileName(
1131 profiles::GetAvatarNameForProfile(browser_->profile()->GetPath()),
1133 layout->StartRowWithPadding(1, 0, 0,
1134 views::kRelatedControlSmallVerticalSpacing);
1135 layout->StartRow(1, 0);
1136 layout->AddView(current_profile_name_);
1141 // The available links depend on the type of profile that is active.
1142 if (avatar_item.signed_in) {
1143 layout->StartRow(1, 0);
1144 if (switches::IsEnableAccountConsistency()) {
1145 base::string16 link_title = l10n_util::GetStringUTF16(
1146 view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER ?
1147 IDS_PROFILES_PROFILE_MANAGE_ACCOUNTS_BUTTON :
1148 IDS_PROFILES_PROFILE_HIDE_MANAGE_ACCOUNTS_BUTTON);
1149 manage_accounts_link_ = CreateLink(link_title, this);
1150 manage_accounts_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
1151 layout->AddView(manage_accounts_link_);
1153 // Add a small padding between the email button and the profile name.
1154 layout->StartRowWithPadding(1, 0, 0, 2);
1155 // Badge the email address if there's an authentication error.
1156 if (HasAuthError(browser_->profile())) {
1157 const gfx::ImageSkia warning_image = *rb->GetImageNamed(
1158 IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia();
1159 auth_error_email_button_ =
1160 new RightAlignedIconLabelButton(this, avatar_item.sync_state);
1161 auth_error_email_button_->SetElideBehavior(gfx::ELIDE_EMAIL);
1162 auth_error_email_button_->SetBorder(views::Border::NullBorder());
1163 auth_error_email_button_->SetImage(
1164 views::LabelButton::STATE_NORMAL, warning_image);
1165 auth_error_email_button_->SetTextColor(
1166 views::LabelButton::STATE_NORMAL,
1167 views::Link::GetDefaultEnabledColor());
1168 auth_error_email_button_->SetFocusable(true);
1169 layout->AddView(auth_error_email_button_);
1171 views::Label* email_label = new views::Label(avatar_item.sync_state);
1172 email_label->SetElideBehavior(gfx::ELIDE_EMAIL);
1173 email_label->SetEnabled(false);
1174 layout->AddView(email_label);
1178 SigninManagerBase* signin_manager = SigninManagerFactory::GetForProfile(
1179 browser_->profile()->GetOriginalProfile());
1180 if (signin_manager->IsSigninAllowed()) {
1181 views::Label* promo = new views::Label(
1182 l10n_util::GetStringUTF16(IDS_PROFILES_SIGNIN_PROMO));
1183 promo->SetMultiLine(true);
1184 promo->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1185 layout->StartRowWithPadding(1, 0, 0,
1186 views::kRelatedControlSmallVerticalSpacing);
1187 layout->StartRow(1, 0);
1188 layout->AddView(promo);
1190 signin_current_profile_link_ = new views::BlueButton(
1191 this, l10n_util::GetStringFUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL,
1192 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)));
1193 layout->StartRowWithPadding(1, 0, 0,
1194 views::kRelatedControlVerticalSpacing);
1195 layout->StartRow(1, 0);
1196 layout->AddView(signin_current_profile_link_);
1203 views::View* ProfileChooserView::CreateGuestProfileView() {
1204 gfx::Image guest_icon =
1205 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
1206 profiles::GetPlaceholderAvatarIconResourceID());
1207 AvatarMenu::Item guest_avatar_item(0, 0, guest_icon);
1208 guest_avatar_item.active = true;
1209 guest_avatar_item.name = l10n_util::GetStringUTF16(
1210 IDS_PROFILES_GUEST_PROFILE_NAME);
1211 guest_avatar_item.signed_in = false;
1213 return CreateCurrentProfileView(guest_avatar_item, true);
1216 views::View* ProfileChooserView::CreateOtherProfilesView(
1217 const Indexes& avatars_to_show) {
1218 views::View* view = new views::View();
1219 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
1221 int num_avatars_to_show = avatars_to_show.size();
1222 for (int i = 0; i < num_avatars_to_show; ++i) {
1223 const size_t index = avatars_to_show[i];
1224 const AvatarMenu::Item& item = avatar_menu_->GetItemAt(index);
1225 const int kSmallImageSide = 32;
1227 gfx::Image image = profiles::GetSizedAvatarIcon(
1228 item.icon, true, kSmallImageSide, kSmallImageSide);
1230 views::LabelButton* button = new BackgroundColorHoverButton(
1233 *image.ToImageSkia());
1234 open_other_profile_indexes_map_[button] = index;
1236 layout->StartRow(1, 0);
1237 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1238 layout->StartRow(1, 0);
1239 layout->AddView(button);
1245 views::View* ProfileChooserView::CreateOptionsView(bool enable_lock) {
1246 views::View* view = new views::View();
1247 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
1249 base::string16 text = browser_->profile()->IsGuestSession() ?
1250 l10n_util::GetStringUTF16(IDS_PROFILES_EXIT_GUEST) :
1251 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_USERS_BUTTON);
1252 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1253 users_button_ = new BackgroundColorHoverButton(
1256 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
1257 layout->StartRow(1, 0);
1258 layout->AddView(users_button_);
1260 if (ShouldShowGoIncognito()) {
1261 layout->StartRow(1, 0);
1262 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1264 go_incognito_button_ = new BackgroundColorHoverButton(
1266 l10n_util::GetStringUTF16(IDS_PROFILES_GO_INCOGNITO_BUTTON),
1267 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_INCOGNITO));
1268 layout->StartRow(1, 0);
1269 layout->AddView(go_incognito_button_);
1273 layout->StartRow(1, 0);
1274 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1276 lock_button_ = new BackgroundColorHoverButton(
1278 l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON),
1279 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_LOCK));
1280 layout->StartRow(1, 0);
1281 layout->AddView(lock_button_);
1286 views::View* ProfileChooserView::CreateSupervisedUserDisclaimerView() {
1287 views::View* view = new views::View();
1288 views::GridLayout* layout = CreateSingleColumnLayout(
1289 view, kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew);
1290 layout->SetInsets(views::kRelatedControlVerticalSpacing,
1291 views::kButtonHEdgeMarginNew,
1292 views::kRelatedControlVerticalSpacing,
1293 views::kButtonHEdgeMarginNew);
1294 views::Label* disclaimer = new views::Label(
1295 avatar_menu_->GetSupervisedUserInformation());
1296 disclaimer->SetMultiLine(true);
1297 disclaimer->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1298 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1299 disclaimer->SetFontList(rb->GetFontList(ui::ResourceBundle::SmallFont));
1300 layout->StartRow(1, 0);
1301 layout->AddView(disclaimer);
1306 views::View* ProfileChooserView::CreateCurrentProfileAccountsView(
1307 const AvatarMenu::Item& avatar_item) {
1308 DCHECK(avatar_item.signed_in);
1309 views::View* view = new views::View();
1310 view->set_background(views::Background::CreateSolidBackground(
1311 profiles::kAvatarBubbleAccountsBackgroundColor));
1312 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
1314 Profile* profile = browser_->profile();
1315 std::string primary_account =
1316 SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedUsername();
1317 DCHECK(!primary_account.empty());
1318 std::vector<std::string>accounts =
1319 profiles::GetSecondaryAccountsForProfile(profile, primary_account);
1321 // Get state of authentication error, if any.
1322 std::string error_account_id = GetAuthErrorAccountId(profile);
1324 // The primary account should always be listed first.
1325 // TODO(rogerta): we still need to further differentiate the primary account
1326 // from the others in the UI, so more work is likely required here:
1327 // crbug.com/311124.
1328 CreateAccountButton(layout, primary_account, true,
1329 error_account_id == primary_account, kFixedMenuWidth);
1330 for (size_t i = 0; i < accounts.size(); ++i)
1331 CreateAccountButton(layout, accounts[i], false,
1332 error_account_id == accounts[i], kFixedMenuWidth);
1334 if (!profile->IsSupervised()) {
1335 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
1337 add_account_link_ = CreateLink(l10n_util::GetStringFUTF16(
1338 IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, avatar_item.name), this);
1339 add_account_link_->SetBorder(views::Border::CreateEmptyBorder(
1340 0, views::kButtonVEdgeMarginNew,
1341 views::kRelatedControlVerticalSpacing, 0));
1342 layout->StartRow(1, 0);
1343 layout->AddView(add_account_link_);
1349 void ProfileChooserView::CreateAccountButton(views::GridLayout* layout,
1350 const std::string& account,
1351 bool is_primary_account,
1352 bool reauth_required,
1354 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1355 const gfx::ImageSkia* delete_default_image =
1356 rb->GetImageNamed(IDR_CLOSE_1).ToImageSkia();
1357 const int kDeleteButtonWidth = delete_default_image->width();
1358 const gfx::ImageSkia warning_default_image = reauth_required ?
1359 *rb->GetImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia() :
1361 const int kWarningButtonWidth = reauth_required ?
1362 warning_default_image.width() + views::kRelatedButtonHSpacing : 0;
1363 int available_width = width - 2 * views::kButtonHEdgeMarginNew
1364 - kDeleteButtonWidth - kWarningButtonWidth;
1365 views::LabelButton* email_button = new BackgroundColorHoverButton(
1366 reauth_required ? this : NULL,
1367 base::UTF8ToUTF16(account),
1368 warning_default_image);
1369 email_button->SetElideBehavior(gfx::ELIDE_EMAIL);
1370 email_button->SetMinSize(gfx::Size(0, kButtonHeight));
1371 email_button->SetMaxSize(gfx::Size(available_width, kButtonHeight));
1372 layout->StartRow(1, 0);
1373 layout->AddView(email_button);
1375 if (reauth_required)
1376 reauth_account_button_map_[email_button] = account;
1379 if (!browser_->profile()->IsSupervised()) {
1380 views::ImageButton* delete_button = new views::ImageButton(this);
1381 delete_button->SetImageAlignment(views::ImageButton::ALIGN_RIGHT,
1382 views::ImageButton::ALIGN_MIDDLE);
1383 delete_button->SetImage(views::ImageButton::STATE_NORMAL,
1384 delete_default_image);
1385 delete_button->SetImage(views::ImageButton::STATE_HOVERED,
1386 rb->GetImageSkiaNamed(IDR_CLOSE_1_H));
1387 delete_button->SetImage(views::ImageButton::STATE_PRESSED,
1388 rb->GetImageSkiaNamed(IDR_CLOSE_1_P));
1389 delete_button->SetBounds(
1390 width - views::kButtonHEdgeMarginNew - kDeleteButtonWidth,
1391 0, kDeleteButtonWidth, kButtonHeight);
1393 email_button->set_notify_enter_exit_on_child(true);
1394 email_button->AddChildView(delete_button);
1396 // Save the original email address, as the button text could be elided.
1397 delete_account_button_map_[delete_button] = account;
1401 views::View* ProfileChooserView::CreateGaiaSigninView() {
1405 switch (view_mode_) {
1406 case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
1407 url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_SIGN_IN,
1408 false /* auto_close */,
1409 true /* is_constrained */);
1410 message_id = IDS_PROFILES_GAIA_SIGNIN_TITLE;
1412 case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
1413 url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT,
1414 false /* auto_close */,
1415 true /* is_constrained */);
1416 message_id = IDS_PROFILES_GAIA_ADD_ACCOUNT_TITLE;
1418 case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH: {
1419 DCHECK(HasAuthError(browser_->profile()));
1420 url = signin::GetReauthURL(browser_->profile(),
1421 GetAuthErrorUsername(browser_->profile()));
1422 message_id = IDS_PROFILES_GAIA_REAUTH_TITLE;
1426 NOTREACHED() << "Called with invalid mode=" << view_mode_;
1430 // Adds Gaia signin webview
1431 Profile* profile = browser_->profile();
1432 views::WebView* web_view = new views::WebView(profile);
1433 web_view->LoadInitialURL(url);
1434 web_view->SetPreferredSize(
1435 gfx::Size(kFixedGaiaViewWidth, kFixedGaiaViewHeight));
1437 TitleCard* title_card = new TitleCard(l10n_util::GetStringUTF16(message_id),
1439 &gaia_signin_cancel_button_);
1440 return TitleCard::AddPaddedTitleCard(
1441 web_view, title_card, kFixedGaiaViewWidth);
1444 views::View* ProfileChooserView::CreateAccountRemovalView() {
1445 views::View* view = new views::View();
1446 views::GridLayout* layout = CreateSingleColumnLayout(
1447 view, kFixedAccountRemovalViewWidth - 2 * views::kButtonHEdgeMarginNew);
1448 layout->SetInsets(0,
1449 views::kButtonHEdgeMarginNew,
1450 views::kButtonVEdgeMarginNew,
1451 views::kButtonHEdgeMarginNew);
1453 const std::string& primary_account = SigninManagerFactory::GetForProfile(
1454 browser_->profile())->GetAuthenticatedUsername();
1455 bool is_primary_account = primary_account == account_id_to_remove_;
1458 layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing);
1459 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1460 const gfx::FontList& small_font_list =
1461 rb->GetFontList(ui::ResourceBundle::SmallFont);
1463 if (is_primary_account) {
1464 std::vector<size_t> offsets;
1465 const base::string16 settings_text =
1466 l10n_util::GetStringUTF16(IDS_PROFILES_SETTINGS_LINK);
1467 const base::string16 primary_account_removal_text =
1468 l10n_util::GetStringFUTF16(IDS_PROFILES_PRIMARY_ACCOUNT_REMOVAL_TEXT,
1469 base::UTF8ToUTF16(account_id_to_remove_), settings_text, &offsets);
1470 views::StyledLabel* primary_account_removal_label =
1471 new views::StyledLabel(primary_account_removal_text, this);
1472 primary_account_removal_label->AddStyleRange(
1473 gfx::Range(offsets[1], offsets[1] + settings_text.size()),
1474 views::StyledLabel::RangeStyleInfo::CreateForLink());
1475 primary_account_removal_label->SetBaseFontList(small_font_list);
1476 layout->AddView(primary_account_removal_label);
1478 views::Label* content_label = new views::Label(
1479 l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TEXT));
1480 content_label->SetMultiLine(true);
1481 content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1482 content_label->SetFontList(small_font_list);
1483 layout->AddView(content_label);
1487 if (!is_primary_account) {
1488 remove_account_button_ = new views::BlueButton(
1489 this, l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_BUTTON));
1490 remove_account_button_->SetHorizontalAlignment(
1492 layout->StartRowWithPadding(
1493 1, 0, 0, views::kUnrelatedControlVerticalSpacing);
1494 layout->AddView(remove_account_button_);
1496 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
1499 TitleCard* title_card = new TitleCard(
1500 l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TITLE),
1501 this, &account_removal_cancel_button_);
1502 return TitleCard::AddPaddedTitleCard(view, title_card,
1503 kFixedAccountRemovalViewWidth);
1506 views::View* ProfileChooserView::CreateWelcomeUpgradeTutorialViewIfNeeded(
1507 bool tutorial_shown, const AvatarMenu::Item& avatar_item){
1508 Profile* profile = browser_->profile();
1510 const int show_count = profile->GetPrefs()->GetInteger(
1511 prefs::kProfileAvatarTutorialShown);
1512 // Do not show the tutorial if user has dismissed it.
1513 if (show_count > signin_ui_util::kUpgradeWelcomeTutorialShowMax)
1516 if (!tutorial_shown) {
1517 if (show_count == signin_ui_util::kUpgradeWelcomeTutorialShowMax)
1519 profile->GetPrefs()->SetInteger(
1520 prefs::kProfileAvatarTutorialShown, show_count + 1);
1522 ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
1523 ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_VIEW);
1525 // For local profiles, the "Not you" link doesn't make sense.
1526 base::string16 link_message = avatar_item.signed_in ?
1527 l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatar_item.name) :
1530 return CreateTutorialView(
1531 profiles::TUTORIAL_MODE_WELCOME_UPGRADE,
1532 l10n_util::GetStringUTF16(
1533 IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE),
1534 l10n_util::GetStringUTF16(
1535 IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT),
1537 l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON),
1538 true /* stack_button */,
1539 &tutorial_not_you_link_,
1540 &tutorial_see_whats_new_button_,
1541 &tutorial_close_button_);
1544 views::View* ProfileChooserView::CreateSigninConfirmationView() {
1545 ProfileMetrics::LogProfileNewAvatarMenuSignin(
1546 ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_VIEW);
1548 return CreateTutorialView(
1549 profiles::TUTORIAL_MODE_CONFIRM_SIGNIN,
1550 l10n_util::GetStringUTF16(IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE),
1551 l10n_util::GetStringUTF16(
1552 IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT),
1553 l10n_util::GetStringUTF16(IDS_PROFILES_SYNC_SETTINGS_LINK),
1554 l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_OK_BUTTON),
1555 false /* stack_button */,
1556 &tutorial_sync_settings_link_,
1557 &tutorial_sync_settings_ok_button_,
1558 NULL /* close_button*/);
1561 views::View* ProfileChooserView::CreateSigninErrorView() {
1562 LoginUIService* login_ui_service =
1563 LoginUIServiceFactory::GetForProfile(browser_->profile());
1564 base::string16 last_login_result(login_ui_service->GetLastLoginResult());
1565 return CreateTutorialView(
1566 profiles::TUTORIAL_MODE_SHOW_ERROR,
1567 l10n_util::GetStringUTF16(IDS_PROFILES_ERROR_TUTORIAL_TITLE),
1569 l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_TUTORIAL_LEARN_MORE),
1571 false /* stack_button */,
1572 &tutorial_learn_more_link_,
1574 &tutorial_close_button_);
1577 views::View* ProfileChooserView::CreateSwitchUserView() {
1578 views::View* view = new views::View();
1579 views::GridLayout* layout = CreateSingleColumnLayout(
1580 view, kFixedSwitchUserViewWidth);
1581 views::ColumnSet* columns = layout->AddColumnSet(1);
1582 columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
1584 kFixedSwitchUserViewWidth - 2 * views::kButtonHEdgeMarginNew;
1585 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
1586 views::GridLayout::FIXED, label_width, label_width);
1587 columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
1590 layout->StartRowWithPadding(1, 1, 0, views::kUnrelatedControlVerticalSpacing);
1591 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1592 const gfx::FontList& small_font_list =
1593 rb->GetFontList(ui::ResourceBundle::SmallFont);
1594 const AvatarMenu::Item& avatar_item =
1595 avatar_menu_->GetItemAt(avatar_menu_->GetActiveProfileIndex());
1596 views::Label* content_label = new views::Label(
1597 l10n_util::GetStringFUTF16(
1598 IDS_PROFILES_NOT_YOU_CONTENT_TEXT, avatar_item.name));
1599 content_label->SetMultiLine(true);
1600 content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1601 content_label->SetFontList(small_font_list);
1602 layout->AddView(content_label);
1604 // Adds "Add person" button.
1605 layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing);
1606 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1608 add_person_button_ = new BackgroundColorHoverButton(
1610 l10n_util::GetStringUTF16(IDS_PROFILES_ADD_PERSON_BUTTON),
1611 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
1612 layout->StartRow(1, 0);
1613 layout->AddView(add_person_button_);
1615 // Adds "Disconnect your Google Account" button.
1616 layout->StartRow(1, 0);
1617 layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1619 disconnect_button_ = new BackgroundColorHoverButton(
1621 l10n_util::GetStringUTF16(IDS_PROFILES_DISCONNECT_BUTTON),
1622 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_DISCONNECT));
1623 layout->StartRow(1, 0);
1624 layout->AddView(disconnect_button_);
1626 TitleCard* title_card = new TitleCard(
1627 l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatar_item.name),
1628 this, &switch_user_cancel_button_);
1629 return TitleCard::AddPaddedTitleCard(view, title_card,
1630 kFixedSwitchUserViewWidth);
1633 bool ProfileChooserView::ShouldShowGoIncognito() const {
1634 bool incognito_available =
1635 IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) !=
1636 IncognitoModePrefs::DISABLED;
1637 return incognito_available && !browser_->profile()->IsGuestSession();
1640 void ProfileChooserView::PostActionPerformed(
1641 ProfileMetrics::ProfileDesktopMenu action_performed) {
1642 ProfileMetrics::LogProfileDesktopMenu(action_performed, gaia_service_type_);
1643 gaia_service_type_ = signin::GAIA_SERVICE_TYPE_NONE;