profile_chooser_view.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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.
4
5#include "chrome/browser/ui/views/profiles/profile_chooser_view.h"
6
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/first_run/first_run.h"
11#include "chrome/browser/lifetime/application_lifetime.h"
12#include "chrome/browser/prefs/incognito_mode_prefs.h"
13#include "chrome/browser/profiles/profile_avatar_icon_util.h"
14#include "chrome/browser/profiles/profile_info_cache.h"
15#include "chrome/browser/profiles/profile_manager.h"
16#include "chrome/browser/profiles/profile_metrics.h"
17#include "chrome/browser/profiles/profile_window.h"
18#include "chrome/browser/profiles/profiles_state.h"
19#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
20#include "chrome/browser/signin/signin_header_helper.h"
21#include "chrome/browser/signin/signin_manager_factory.h"
22#include "chrome/browser/signin/signin_promo.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"
64
65namespace {
66
67// Helpers --------------------------------------------------------------------
68
69const int kFixedMenuWidth = 250;
70const int kButtonHeight = 32;
71const int kFixedGaiaViewHeight = 400;
72const int kFixedGaiaViewWidth = 360;
73const int kFixedAccountRemovalViewWidth = 280;
74const int kFixedSwitchUserViewWidth = 280;
75const int kLargeImageSide = 88;
76
77// The maximum number of times to show the welcome tutorial for an upgrade user.
78const int kUpgradeWelcomeTutorialShowMax = 1;
79
80// Creates a GridLayout with a single column. This ensures that all the child
81// views added get auto-expanded to fill the full width of the bubble.
82views::GridLayout* CreateSingleColumnLayout(views::View* view, int width) {
83  views::GridLayout* layout = new views::GridLayout(view);
84  view->SetLayoutManager(layout);
85
86  views::ColumnSet* columns = layout->AddColumnSet(0);
87  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
88                     views::GridLayout::FIXED, width, width);
89  return layout;
90}
91
92views::Link* CreateLink(const base::string16& link_text,
93                        views::LinkListener* listener) {
94  views::Link* link_button = new views::Link(link_text);
95  link_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
96  link_button->SetUnderline(false);
97  link_button->set_listener(listener);
98  return link_button;
99}
100
101gfx::ImageSkia CreateSquarePlaceholderImage(int size) {
102  SkBitmap bitmap;
103  bitmap.allocPixels(SkImageInfo::MakeA8(size, size));
104  bitmap.eraseARGB(0, 0, 0, 0);
105  return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
106}
107
108bool HasAuthError(Profile* profile) {
109  const SigninErrorController* error =
110      profiles::GetSigninErrorController(profile);
111  return error && error->HasError();
112}
113
114std::string GetAuthErrorAccountId(Profile* profile) {
115  const SigninErrorController* error =
116      profiles::GetSigninErrorController(profile);
117  if (!error)
118    return std::string();
119
120  return error->error_account_id();
121}
122
123std::string GetAuthErrorUsername(Profile* profile) {
124  const SigninErrorController* error =
125      profiles::GetSigninErrorController(profile);
126  if (!error)
127    return std::string();
128
129  return error->error_username();
130}
131
132// BackgroundColorHoverButton -------------------------------------------------
133
134// A custom button that allows for setting a background color when hovered over.
135class BackgroundColorHoverButton : public views::LabelButton {
136 public:
137  BackgroundColorHoverButton(views::ButtonListener* listener,
138                             const base::string16& text,
139                             const gfx::ImageSkia& icon)
140      : views::LabelButton(listener, text) {
141    SetBorder(views::Border::CreateEmptyBorder(
142        0, views::kButtonHEdgeMarginNew, 0, views::kButtonHEdgeMarginNew));
143    SetMinSize(gfx::Size(0,
144        kButtonHeight + views::kRelatedControlVerticalSpacing));
145    SetImage(STATE_NORMAL, icon);
146  }
147
148  virtual ~BackgroundColorHoverButton() {}
149
150 private:
151  // views::LabelButton:
152  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
153    if ((state() == STATE_PRESSED) ||
154        (state() == STATE_HOVERED) ||
155        HasFocus()) {
156      canvas->DrawColor(GetNativeTheme()->GetSystemColor(
157          ui::NativeTheme::kColorId_ButtonHoverBackgroundColor));
158    }
159    LabelButton::OnPaint(canvas);
160  }
161
162  DISALLOW_COPY_AND_ASSIGN(BackgroundColorHoverButton);
163};
164
165// SizedContainer -------------------------------------------------
166
167// A simple container view that takes an explicit preferred size.
168class SizedContainer : public views::View {
169 public:
170  explicit SizedContainer(const gfx::Size& preferred_size)
171      : preferred_size_(preferred_size) {}
172
173  virtual gfx::Size GetPreferredSize() const OVERRIDE {
174    return preferred_size_;
175  }
176
177 private:
178  gfx::Size preferred_size_;
179};
180
181}  // namespace
182
183// RightAlignedIconLabelButton -------------------------------------------------
184
185// A custom LabelButton that has a centered text and right aligned icon.
186class RightAlignedIconLabelButton : public views::LabelButton {
187 public:
188  RightAlignedIconLabelButton(views::ButtonListener* listener,
189                              const base::string16& text)
190      : views::LabelButton(listener, text) {
191  }
192
193 protected:
194  virtual void Layout() OVERRIDE {
195    // This layout trick keeps the text left-aligned and the icon right-aligned.
196    SetHorizontalAlignment(gfx::ALIGN_RIGHT);
197    views::LabelButton::Layout();
198    label()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
199  }
200
201  DISALLOW_COPY_AND_ASSIGN(RightAlignedIconLabelButton);
202};
203
204// EditableProfilePhoto -------------------------------------------------
205
206// A custom Image control that shows a "change" button when moused over.
207class EditableProfilePhoto : public views::ImageView {
208 public:
209  EditableProfilePhoto(views::ButtonListener* listener,
210                       const gfx::Image& icon,
211                       bool is_editing_allowed,
212                       const gfx::Rect& bounds)
213      : views::ImageView(),
214        change_photo_button_(NULL) {
215    gfx::Image image = profiles::GetSizedAvatarIcon(
216        icon, true, kLargeImageSide, kLargeImageSide);
217    SetImage(image.ToImageSkia());
218    SetBoundsRect(bounds);
219
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));
224
225    if (!is_editing_allowed)
226      return;
227
228    set_notify_enter_exit_on_child(true);
229
230    // Button overlay that appears when hovering over the image.
231    change_photo_button_ = new views::LabelButton(listener, base::string16());
232    change_photo_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
233    change_photo_button_->SetBorder(views::Border::NullBorder());
234
235    const SkColor kBackgroundColor = SkColorSetARGB(65, 255, 255, 255);
236    change_photo_button_->set_background(
237        views::Background::CreateSolidBackground(kBackgroundColor));
238    change_photo_button_->SetImage(views::LabelButton::STATE_NORMAL,
239        *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
240            IDR_ICON_PROFILES_EDIT_CAMERA));
241
242    change_photo_button_->SetSize(bounds.size());
243    change_photo_button_->SetVisible(false);
244    AddChildView(change_photo_button_);
245  }
246
247  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
248    // Display the profile picture as a circle.
249    canvas->ClipPath(circular_mask_, true);
250    views::ImageView::OnPaint(canvas);
251  }
252
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);
258  }
259
260  views::LabelButton* change_photo_button() { return change_photo_button_; }
261
262 private:
263  // views::View:
264  virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
265    if (change_photo_button_)
266      change_photo_button_->SetVisible(true);
267  }
268
269  virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
270    if (change_photo_button_)
271      change_photo_button_->SetVisible(false);
272  }
273
274  gfx::Path circular_mask_;
275
276  // Button that is shown when hovering over the image view. Can be NULL if
277  // the photo isn't allowed to be edited (e.g. for guest profiles).
278  views::LabelButton* change_photo_button_;
279
280  DISALLOW_COPY_AND_ASSIGN(EditableProfilePhoto);
281};
282
283// EditableProfileName -------------------------------------------------
284
285// A custom text control that turns into a textfield for editing when clicked.
286class EditableProfileName : public RightAlignedIconLabelButton,
287                            public views::ButtonListener {
288 public:
289  EditableProfileName(views::TextfieldController* controller,
290                      const base::string16& text,
291                      bool is_editing_allowed)
292      : RightAlignedIconLabelButton(this, text),
293        profile_name_textfield_(NULL) {
294    ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
295    const gfx::FontList& medium_font_list =
296        rb->GetFontList(ui::ResourceBundle::MediumFont);
297    SetFontList(medium_font_list);
298    SetHorizontalAlignment(gfx::ALIGN_CENTER);
299
300    if (!is_editing_allowed) {
301      SetBorder(views::Border::CreateEmptyBorder(2, 0, 2, 0));
302      return;
303    }
304
305    // Show an "edit" pencil icon when hovering over. In the default state,
306    // we need to create an empty placeholder of the correct size, so that
307    // the text doesn't jump around when the hovered icon appears.
308    gfx::ImageSkia hover_image =
309        *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_HOVER);
310    SetImage(STATE_NORMAL, CreateSquarePlaceholderImage(hover_image.width()));
311    SetImage(STATE_HOVERED, hover_image);
312    SetImage(STATE_PRESSED,
313             *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_PRESSED));
314    // To center the text, we need to offest it by the width of the icon we
315    // are adding and its padding. We need to also add a small top/bottom
316    // padding to account for the textfield's border.
317    const int kIconTextLabelButtonSpacing = 5;
318    SetBorder(views::Border::CreateEmptyBorder(
319        2, hover_image.width() + kIconTextLabelButtonSpacing, 2, 0));
320
321    // Textfield that overlaps the button.
322    profile_name_textfield_ = new views::Textfield();
323    profile_name_textfield_->set_controller(controller);
324    profile_name_textfield_->SetFontList(medium_font_list);
325    profile_name_textfield_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
326
327    profile_name_textfield_->SetVisible(false);
328    AddChildView(profile_name_textfield_);
329  }
330
331  views::Textfield* profile_name_textfield() {
332    return profile_name_textfield_;
333  }
334
335  // Hide the editable textfield to show the profile name button instead.
336  void ShowReadOnlyView() {
337    if (profile_name_textfield_)
338      profile_name_textfield_->SetVisible(false);
339  }
340
341 private:
342  // views::ButtonListener:
343  virtual void ButtonPressed(views::Button* sender,
344                            const ui::Event& event) OVERRIDE {
345    if (profile_name_textfield_) {
346      profile_name_textfield_->SetVisible(true);
347      profile_name_textfield_->SetText(GetText());
348      profile_name_textfield_->SelectAll(false);
349      profile_name_textfield_->RequestFocus();
350    }
351  }
352
353  // views::LabelButton:
354  virtual bool OnKeyReleased(const ui::KeyEvent& event) OVERRIDE {
355    // Override CustomButton's implementation, which presses the button when
356    // you press space and clicks it when you release space, as the space can be
357    // part of the new profile name typed in the textfield.
358    return false;
359  }
360
361  virtual void Layout() OVERRIDE {
362    if (profile_name_textfield_)
363      profile_name_textfield_->SetBounds(0, 0, width(), height());
364    RightAlignedIconLabelButton::Layout();
365  }
366
367  // Textfield that is shown when editing the profile name. Can be NULL if
368  // the profile name isn't allowed to be edited (e.g. for guest profiles).
369  views::Textfield* profile_name_textfield_;
370
371  DISALLOW_COPY_AND_ASSIGN(EditableProfileName);
372};
373
374// A title card with one back button right aligned and one label center aligned.
375class TitleCard : public views::View {
376 public:
377  TitleCard(const base::string16& message, views::ButtonListener* listener,
378            views::ImageButton** back_button) {
379    back_button_ = new views::ImageButton(listener);
380    back_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
381                                    views::ImageButton::ALIGN_MIDDLE);
382    ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
383    back_button_->SetImage(views::ImageButton::STATE_NORMAL,
384                           rb->GetImageSkiaNamed(IDR_BACK));
385    back_button_->SetImage(views::ImageButton::STATE_HOVERED,
386                           rb->GetImageSkiaNamed(IDR_BACK_H));
387    back_button_->SetImage(views::ImageButton::STATE_PRESSED,
388                           rb->GetImageSkiaNamed(IDR_BACK_P));
389    back_button_->SetImage(views::ImageButton::STATE_DISABLED,
390                           rb->GetImageSkiaNamed(IDR_BACK_D));
391    *back_button = back_button_;
392
393    title_label_ = new views::Label(message);
394    title_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
395    const gfx::FontList& medium_font_list =
396        rb->GetFontList(ui::ResourceBundle::MediumFont);
397    title_label_->SetFontList(medium_font_list);
398
399    AddChildView(back_button_);
400    AddChildView(title_label_);
401  }
402
403  // Creates a new view that has the |title_card| with padding at the top, an
404  // edge-to-edge separator below, and the specified |view| at the bottom.
405  static views::View* AddPaddedTitleCard(views::View* view,
406                                         TitleCard* title_card,
407                                         int width) {
408    views::View* titled_view = new views::View();
409    views::GridLayout* layout = new views::GridLayout(titled_view);
410    titled_view->SetLayoutManager(layout);
411
412    // Column set 0 is a single column layout with horizontal padding at left
413    // and right, and column set 1 is a single column layout with no padding.
414    views::ColumnSet* columns = layout->AddColumnSet(0);
415    columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew);
416    int available_width = width - 2 * views::kButtonHEdgeMarginNew;
417    columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
418        views::GridLayout::FIXED, available_width, available_width);
419    columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew);
420    layout->AddColumnSet(1)->AddColumn(views::GridLayout::FILL,
421        views::GridLayout::FILL, 0,views::GridLayout::FIXED, width, width);
422
423    layout->StartRowWithPadding(1, 0, 0, views::kButtonVEdgeMarginNew);
424    layout->AddView(title_card);
425    layout->StartRowWithPadding(1, 1, 0, views::kRelatedControlVerticalSpacing);
426    layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
427
428    layout->StartRow(1, 1);
429    layout->AddView(view);
430
431    return titled_view;
432  }
433
434 private:
435  virtual void Layout() OVERRIDE{
436    back_button_->SetBounds(
437        0, 0, back_button_->GetPreferredSize().width(), height());
438    title_label_->SetBoundsRect(GetContentsBounds());
439  }
440
441  virtual gfx::Size GetPreferredSize() const OVERRIDE{
442    int height = std::max(title_label_->GetPreferredSize().height(),
443        back_button_->GetPreferredSize().height());
444    return gfx::Size(width(), height);
445  }
446
447  views::ImageButton* back_button_;
448  views::Label* title_label_;
449
450  DISALLOW_COPY_AND_ASSIGN(TitleCard);
451};
452
453// ProfileChooserView ---------------------------------------------------------
454
455// static
456ProfileChooserView* ProfileChooserView::profile_bubble_ = NULL;
457bool ProfileChooserView::close_on_deactivate_for_testing_ = true;
458
459// static
460void ProfileChooserView::ShowBubble(
461    profiles::BubbleViewMode view_mode,
462    const signin::ManageAccountsParams& manage_accounts_params,
463    views::View* anchor_view,
464    views::BubbleBorder::Arrow arrow,
465    views::BubbleBorder::BubbleAlignment border_alignment,
466    Browser* browser) {
467  if (IsShowing())
468    return;
469
470  profile_bubble_ = new ProfileChooserView(anchor_view, arrow, browser,
471      view_mode, manage_accounts_params.service_type);
472  views::BubbleDelegateView::CreateBubble(profile_bubble_);
473  profile_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_);
474  profile_bubble_->SetAlignment(border_alignment);
475  profile_bubble_->GetWidget()->Show();
476  profile_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
477}
478
479// static
480bool ProfileChooserView::IsShowing() {
481  return profile_bubble_ != NULL;
482}
483
484// static
485void ProfileChooserView::Hide() {
486  if (IsShowing())
487    profile_bubble_->GetWidget()->Close();
488}
489
490ProfileChooserView::ProfileChooserView(views::View* anchor_view,
491                                       views::BubbleBorder::Arrow arrow,
492                                       Browser* browser,
493                                       profiles::BubbleViewMode view_mode,
494                                       signin::GAIAServiceType service_type)
495    : BubbleDelegateView(anchor_view, arrow),
496      browser_(browser),
497      view_mode_(view_mode),
498      tutorial_mode_(profiles::TUTORIAL_MODE_NONE),
499      gaia_service_type_(service_type) {
500  // Reset the default margins inherited from the BubbleDelegateView.
501  // Add a small top/bottom inset so that the bubble's rounded corners show up.
502  set_margins(gfx::Insets(1, 0, 1, 0));
503  set_background(views::Background::CreateSolidBackground(
504      GetNativeTheme()->GetSystemColor(
505          ui::NativeTheme::kColorId_DialogBackground)));
506  ResetView();
507
508  avatar_menu_.reset(new AvatarMenu(
509      &g_browser_process->profile_manager()->GetProfileInfoCache(),
510      this,
511      browser_));
512  avatar_menu_->RebuildMenu();
513
514  ProfileOAuth2TokenService* oauth2_token_service =
515      ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
516  if (oauth2_token_service)
517    oauth2_token_service->AddObserver(this);
518}
519
520ProfileChooserView::~ProfileChooserView() {
521  ProfileOAuth2TokenService* oauth2_token_service =
522      ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
523  if (oauth2_token_service)
524    oauth2_token_service->RemoveObserver(this);
525}
526
527void ProfileChooserView::ResetView() {
528  open_other_profile_indexes_map_.clear();
529  delete_account_button_map_.clear();
530  reauth_account_button_map_.clear();
531  manage_accounts_link_ = NULL;
532  signin_current_profile_link_ = NULL;
533  auth_error_email_button_ = NULL;
534  current_profile_photo_ = NULL;
535  current_profile_name_ = NULL;
536  users_button_ = NULL;
537  go_incognito_button_ = NULL;
538  lock_button_ = NULL;
539  add_account_link_ = NULL;
540  gaia_signin_cancel_button_ = NULL;
541  remove_account_button_ = NULL;
542  account_removal_cancel_button_ = NULL;
543  add_person_button_ = NULL;
544  disconnect_button_ = NULL;
545  switch_user_cancel_button_ = NULL;
546  tutorial_sync_settings_ok_button_ = NULL;
547  tutorial_sync_settings_link_ = NULL;
548  tutorial_see_whats_new_button_ = NULL;
549  tutorial_not_you_link_ = NULL;
550  tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
551}
552
553void ProfileChooserView::Init() {
554  // If view mode is PROFILE_CHOOSER but there is an auth error, force
555  // ACCOUNT_MANAGEMENT mode.
556  if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER &&
557      HasAuthError(browser_->profile()) &&
558      switches::IsEnableAccountConsistency() &&
559      avatar_menu_->GetItemAt(avatar_menu_->GetActiveProfileIndex()).
560          signed_in) {
561    view_mode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT;
562  }
563
564  ShowView(view_mode_, avatar_menu_.get());
565}
566
567void ProfileChooserView::OnAvatarMenuChanged(
568    AvatarMenu* avatar_menu) {
569  // Do not refresh the avatar menu if the user is on a signin related view.
570  if (view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN ||
571      view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
572      view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
573    return;
574  }
575
576  // Refresh the view with the new menu. We can't just update the local copy
577  // as this may have been triggered by a sign out action, in which case
578  // the view is being destroyed.
579  ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu);
580}
581
582void ProfileChooserView::OnRefreshTokenAvailable(
583    const std::string& account_id) {
584  if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ||
585      view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN ||
586      view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
587      view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
588    if (view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN)
589      tutorial_mode_ = profiles::TUTORIAL_MODE_CONFIRM_SIGNIN;
590    // The account management UI is only available through the
591    // --enable-account-consistency flag.
592    ShowView(switches::IsEnableAccountConsistency() ?
593        profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
594        profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
595  }
596}
597
598void ProfileChooserView::OnRefreshTokenRevoked(const std::string& account_id) {
599  // Refresh the account management view when an account is removed from the
600  // profile.
601  if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT)
602    ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
603}
604
605void ProfileChooserView::ShowView(profiles::BubbleViewMode view_to_display,
606                                  AvatarMenu* avatar_menu) {
607  // The account management view should only be displayed if the active profile
608  // is signed in.
609  const AvatarMenu::Item& active_item = avatar_menu->GetItemAt(
610      avatar_menu->GetActiveProfileIndex());
611  if (view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) {
612    DCHECK(switches::IsEnableAccountConsistency());
613    DCHECK(active_item.signed_in);
614  }
615
616  if (browser_->profile()->IsSupervised() &&
617      (view_to_display == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
618       view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL)) {
619    LOG(WARNING) << "Supervised user attempted to add/remove account";
620    return;
621  }
622
623  // Records the last tutorial mode.
624  profiles::TutorialMode last_tutorial_mode = tutorial_mode_;
625  ResetView();
626  RemoveAllChildViews(true);
627  view_mode_ = view_to_display;
628
629  views::GridLayout* layout;
630  views::View* sub_view;
631  switch (view_mode_) {
632    case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
633    case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
634    case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH:
635      layout = CreateSingleColumnLayout(this, kFixedGaiaViewWidth);
636      sub_view = CreateGaiaSigninView();
637      break;
638    case profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL:
639      layout = CreateSingleColumnLayout(this, kFixedAccountRemovalViewWidth);
640      sub_view = CreateAccountRemovalView();
641      break;
642    case profiles::BUBBLE_VIEW_MODE_SWITCH_USER:
643      layout = CreateSingleColumnLayout(this, kFixedSwitchUserViewWidth);
644      sub_view = CreateSwitchUserView(active_item);
645      break;
646    default:
647      layout = CreateSingleColumnLayout(this, kFixedMenuWidth);
648      sub_view = CreateProfileChooserView(avatar_menu, last_tutorial_mode);
649  }
650  layout->StartRow(1, 0);
651  layout->AddView(sub_view);
652  Layout();
653  if (GetBubbleFrameView())
654    SizeToContents();
655}
656
657void ProfileChooserView::WindowClosing() {
658  DCHECK_EQ(profile_bubble_, this);
659  profile_bubble_ = NULL;
660
661  if (tutorial_mode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
662    LoginUIServiceFactory::GetForProfile(browser_->profile())->
663        SyncConfirmationUIClosed(false /* configure_sync_first */);
664  }
665}
666
667void ProfileChooserView::ButtonPressed(views::Button* sender,
668                                       const ui::Event& event) {
669  // Disable button after clicking so that it doesn't get clicked twice and
670  // start a second action... which can crash Chrome.  But don't disable if it
671  // has no parent (like in tests) because that will also crash.
672  if (sender->parent())
673    sender->SetEnabled(false);
674
675  if (sender == users_button_) {
676    profiles::ShowUserManagerMaybeWithTutorial(browser_->profile());
677    // If this is a guest session, also close all the guest browser windows.
678    if (browser_->profile()->IsGuestSession())
679      profiles::CloseGuestProfileWindows();
680  } else if (sender == go_incognito_button_) {
681    DCHECK(ShouldShowGoIncognito());
682    chrome::NewIncognitoWindow(browser_);
683  } else if (sender == lock_button_) {
684    profiles::LockProfile(browser_->profile());
685    PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_LOCK);
686  } else if (sender == auth_error_email_button_) {
687    ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get());
688  } else if (sender == tutorial_sync_settings_ok_button_) {
689    LoginUIServiceFactory::GetForProfile(browser_->profile())->
690        SyncConfirmationUIClosed(false /* configure_sync_first */);
691    tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
692    ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
693  } else if (sender == tutorial_see_whats_new_button_) {
694    chrome::ShowUserManagerWithTutorial(
695        profiles::USER_MANAGER_TUTORIAL_OVERVIEW);
696  } else if (sender == remove_account_button_) {
697    RemoveAccount();
698  } else if (sender == account_removal_cancel_button_) {
699    account_id_to_remove_.clear();
700    ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
701  } else if (sender == gaia_signin_cancel_button_) {
702    std::string primary_account =
703        SigninManagerFactory::GetForProfile(browser_->profile())->
704        GetAuthenticatedUsername();
705    // The account management view is only available with the
706    // --enable-account-consistency flag.
707    bool account_management_available = !primary_account.empty() &&
708        switches::IsEnableAccountConsistency();
709    ShowView(account_management_available ?
710        profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT :
711        profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
712  } else if (current_profile_photo_ &&
713             sender == current_profile_photo_->change_photo_button()) {
714    avatar_menu_->EditProfile(avatar_menu_->GetActiveProfileIndex());
715    PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE);
716  } else if (sender == signin_current_profile_link_) {
717    ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN, avatar_menu_.get());
718  } else if (sender == add_person_button_) {
719    profiles::ShowUserManagerMaybeWithTutorial(browser_->profile());
720  } else if (sender == disconnect_button_) {
721    chrome::ShowSettings(browser_);
722  } else if (sender == switch_user_cancel_button_) {
723    ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get());
724  } else {
725    // Either one of the "other profiles", or one of the profile accounts
726    // buttons was pressed.
727    ButtonIndexes::const_iterator profile_match =
728        open_other_profile_indexes_map_.find(sender);
729    if (profile_match != open_other_profile_indexes_map_.end()) {
730      avatar_menu_->SwitchToProfile(
731          profile_match->second,
732          ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW,
733          ProfileMetrics::SWITCH_PROFILE_ICON);
734    } else {
735      // This was a profile accounts button.
736      AccountButtonIndexes::const_iterator account_match =
737          delete_account_button_map_.find(sender);
738      if (account_match != delete_account_button_map_.end()) {
739        account_id_to_remove_ = account_match->second;
740        ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL,
741            avatar_menu_.get());
742      } else {
743        account_match = reauth_account_button_map_.find(sender);
744        DCHECK(account_match != reauth_account_button_map_.end());
745        ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get());
746      }
747    }
748  }
749}
750
751void ProfileChooserView::RemoveAccount() {
752  DCHECK(!account_id_to_remove_.empty());
753  MutableProfileOAuth2TokenService* oauth2_token_service =
754      ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(
755      browser_->profile());
756  if (oauth2_token_service) {
757    oauth2_token_service->RevokeCredentials(account_id_to_remove_);
758    PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_REMOVE_ACCT);
759  }
760  account_id_to_remove_.clear();
761
762  ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get());
763}
764
765void ProfileChooserView::LinkClicked(views::Link* sender, int event_flags) {
766  if (sender == manage_accounts_link_) {
767    // This link can either mean show/hide the account management view,
768    // depending on which view it is displayed. ShowView() will DCHECK if
769    // the account management view is displayed for non signed-in users.
770    ShowView(
771        view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ?
772            profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER :
773            profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT,
774        avatar_menu_.get());
775  } else if (sender == add_account_link_) {
776    ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT, avatar_menu_.get());
777    PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT);
778  } else if (sender == tutorial_sync_settings_link_) {
779    LoginUIServiceFactory::GetForProfile(browser_->profile())->
780        SyncConfirmationUIClosed(true /* configure_sync_first */);
781    tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
782  } else {
783    DCHECK(sender == tutorial_not_you_link_);
784    ShowView(profiles::BUBBLE_VIEW_MODE_SWITCH_USER, avatar_menu_.get());
785  }
786}
787
788void ProfileChooserView::StyledLabelLinkClicked(
789    const gfx::Range& range, int event_flags) {
790  chrome::ShowSettings(browser_);
791}
792
793bool ProfileChooserView::HandleKeyEvent(views::Textfield* sender,
794                                        const ui::KeyEvent& key_event) {
795  views::Textfield* name_textfield =
796      current_profile_name_->profile_name_textfield();
797  DCHECK(sender == name_textfield);
798
799  if (key_event.key_code() == ui::VKEY_RETURN ||
800      key_event.key_code() == ui::VKEY_TAB) {
801    // Pressing Tab/Enter commits the new profile name, unless it's empty.
802    base::string16 new_profile_name = name_textfield->text();
803    if (new_profile_name.empty())
804      return true;
805
806    const AvatarMenu::Item& active_item = avatar_menu_->GetItemAt(
807        avatar_menu_->GetActiveProfileIndex());
808    Profile* profile = g_browser_process->profile_manager()->GetProfile(
809        active_item.profile_path);
810    DCHECK(profile);
811
812    if (profile->IsSupervised())
813      return true;
814
815    profiles::UpdateProfileName(profile, new_profile_name);
816    PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME);
817    current_profile_name_->ShowReadOnlyView();
818    return true;
819  }
820  return false;
821}
822
823views::View* ProfileChooserView::CreateProfileChooserView(
824    AvatarMenu* avatar_menu,
825    profiles::TutorialMode last_tutorial_mode) {
826  // TODO(guohui, noms): the view should be customized based on whether new
827  // profile management preview is enabled or not.
828
829  views::View* view = new views::View();
830  views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
831  // Separate items into active and alternatives.
832  Indexes other_profiles;
833  views::View* tutorial_view = NULL;
834  views::View* current_profile_view = NULL;
835  views::View* current_profile_accounts = NULL;
836  views::View* option_buttons_view = NULL;
837  for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) {
838    const AvatarMenu::Item& item = avatar_menu->GetItemAt(i);
839    if (item.active) {
840      option_buttons_view = CreateOptionsView(
841          switches::IsNewProfileManagement() && item.signed_in);
842      current_profile_view = CreateCurrentProfileView(item, false);
843      if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
844        switch (last_tutorial_mode) {
845          case profiles::TUTORIAL_MODE_NONE:
846          case profiles::TUTORIAL_MODE_WELCOME_UPGRADE:
847            tutorial_view = CreateWelcomeUpgradeTutorialViewIfNeeded(
848                last_tutorial_mode == profiles::TUTORIAL_MODE_WELCOME_UPGRADE,
849                item);
850            break;
851          case profiles::TUTORIAL_MODE_CONFIRM_SIGNIN:
852            tutorial_view = CreateSigninConfirmationView();
853            break;
854          case profiles::TUTORIAL_MODE_SHOW_ERROR:
855            // TODO(guohui): not implemented yet.
856            NOTREACHED();
857        }
858      } else {
859        current_profile_accounts = CreateCurrentProfileAccountsView(item);
860      }
861    } else {
862      other_profiles.push_back(i);
863    }
864  }
865
866  if (tutorial_view) {
867    // TODO(mlerman): update UMA stats for the new tutorial.
868    layout->StartRow(1, 0);
869    layout->AddView(tutorial_view);
870  }
871
872  if (!current_profile_view) {
873    // Guest windows don't have an active profile.
874    current_profile_view = CreateGuestProfileView();
875    option_buttons_view = CreateOptionsView(false);
876  }
877
878  layout->StartRow(1, 0);
879  layout->AddView(current_profile_view);
880
881  if (view_mode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
882    DCHECK(current_profile_accounts);
883    layout->StartRow(0, 0);
884    layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
885    layout->StartRow(1, 0);
886    layout->AddView(current_profile_accounts);
887  }
888
889  if (browser_->profile()->IsSupervised()) {
890    layout->StartRow(0, 0);
891    layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
892    layout->StartRow(1, 0);
893    layout->AddView(CreateSupervisedUserDisclaimerView());
894  }
895
896  if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
897    layout->StartRow(1, 0);
898    if (switches::IsFastUserSwitching())
899      layout->AddView(CreateOtherProfilesView(other_profiles));
900  }
901
902  layout->StartRow(0, 0);
903  layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
904
905  if (option_buttons_view) {
906    layout->StartRow(0, 0);
907    layout->AddView(option_buttons_view);
908  }
909
910  return view;
911}
912
913views::View* ProfileChooserView::CreateTutorialView(
914    profiles::TutorialMode tutorial_mode,
915    const base::string16& title_text,
916    const base::string16& content_text,
917    const base::string16& link_text,
918    const base::string16& button_text,
919    views::Link** link,
920    views::LabelButton** button) {
921  tutorial_mode_ = tutorial_mode;
922
923  views::View* view = new views::View();
924  view->set_background(views::Background::CreateSolidBackground(
925      profiles::kAvatarTutorialBackgroundColor));
926  views::GridLayout* layout = CreateSingleColumnLayout(view,
927      kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew);
928  layout->SetInsets(views::kButtonVEdgeMarginNew,
929                    views::kButtonHEdgeMarginNew,
930                    views::kButtonVEdgeMarginNew,
931                    views::kButtonHEdgeMarginNew);
932
933  // Adds title.
934  views::Label* title_label = new views::Label(title_text);
935  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
936  title_label->SetAutoColorReadabilityEnabled(false);
937  title_label->SetEnabledColor(SK_ColorWHITE);
938  title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
939      ui::ResourceBundle::MediumFont));
940  layout->StartRow(1, 0);
941  layout->AddView(title_label);
942
943  // Adds body content.
944  views::Label* content_label = new views::Label(content_text);
945  content_label->SetMultiLine(true);
946  content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
947  content_label->SetAutoColorReadabilityEnabled(false);
948  content_label->SetEnabledColor(profiles::kAvatarTutorialContentTextColor);
949  layout->StartRowWithPadding(1, 0, 0, views::kRelatedControlVerticalSpacing);
950  layout->AddView(content_label);
951
952  // Adds links and buttons.
953  views::ColumnSet* button_columns = layout->AddColumnSet(1);
954  button_columns->AddColumn(views::GridLayout::LEADING,
955      views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
956  button_columns->AddPaddingColumn(
957      1, views::kUnrelatedControlHorizontalSpacing);
958  button_columns->AddColumn(views::GridLayout::TRAILING,
959      views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
960
961  *link = CreateLink(link_text, this);
962  (*link)->SetHorizontalAlignment(gfx::ALIGN_LEFT);
963  (*link)->SetAutoColorReadabilityEnabled(false);
964  (*link)->SetEnabledColor(SK_ColorWHITE);
965  layout->StartRowWithPadding(1, 1, 0, views::kUnrelatedControlVerticalSpacing);
966  layout->AddView(*link);
967
968  *button = new views::LabelButton(this, button_text);
969  (*button)->SetHorizontalAlignment(gfx::ALIGN_CENTER);
970  (*button)->SetStyle(views::Button::STYLE_BUTTON);
971  layout->AddView(*button);
972
973  return view;
974}
975
976views::View* ProfileChooserView::CreateCurrentProfileView(
977    const AvatarMenu::Item& avatar_item,
978    bool is_guest) {
979  views::View* view = new views::View();
980  int column_width = kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew;
981  views::GridLayout* layout = CreateSingleColumnLayout(view, column_width);
982  layout->SetInsets(views::kButtonVEdgeMarginNew,
983                    views::kButtonHEdgeMarginNew,
984                    views::kUnrelatedControlVerticalSpacing,
985                    views::kButtonHEdgeMarginNew);
986
987  // Profile icon, centered.
988  int x_offset = (column_width - kLargeImageSide) / 2;
989  current_profile_photo_ = new EditableProfilePhoto(
990      this, avatar_item.icon, !is_guest,
991      gfx::Rect(x_offset, 0, kLargeImageSide, kLargeImageSide));
992  SizedContainer* profile_icon_container =
993      new SizedContainer(gfx::Size(column_width, kLargeImageSide));
994  profile_icon_container->AddChildView(current_profile_photo_);
995
996  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
997  if (browser_->profile()->IsSupervised()) {
998    views::ImageView* supervised_icon = new views::ImageView();
999    supervised_icon->SetImage(
1000        rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_SUPERVISED));
1001    gfx::Size preferred_size = supervised_icon->GetPreferredSize();
1002    gfx::Rect parent_bounds = current_profile_photo_->bounds();
1003    supervised_icon->SetBounds(
1004        parent_bounds.right() - preferred_size.width(),
1005        parent_bounds.bottom() - preferred_size.height(),
1006        preferred_size.width(),
1007        preferred_size.height());
1008    profile_icon_container->AddChildView(supervised_icon);
1009  }
1010
1011  layout->StartRow(1, 0);
1012  layout->AddView(profile_icon_container);
1013
1014  // Profile name, centered.
1015  bool editing_allowed = !is_guest && !browser_->profile()->IsSupervised();
1016  current_profile_name_ = new EditableProfileName(
1017      this,
1018      profiles::GetAvatarNameForProfile(browser_->profile()->GetPath()),
1019      editing_allowed);
1020  layout->StartRowWithPadding(1, 0, 0,
1021                              views::kRelatedControlSmallVerticalSpacing);
1022  layout->StartRow(1, 0);
1023  layout->AddView(current_profile_name_);
1024
1025  if (is_guest)
1026    return view;
1027
1028  // The available links depend on the type of profile that is active.
1029  if (avatar_item.signed_in) {
1030    layout->StartRow(1, 0);
1031    if (switches::IsEnableAccountConsistency()) {
1032      base::string16 link_title = l10n_util::GetStringUTF16(
1033          view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER ?
1034              IDS_PROFILES_PROFILE_MANAGE_ACCOUNTS_BUTTON :
1035              IDS_PROFILES_PROFILE_HIDE_MANAGE_ACCOUNTS_BUTTON);
1036      manage_accounts_link_ = CreateLink(link_title, this);
1037      manage_accounts_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
1038      layout->AddView(manage_accounts_link_);
1039    } else {
1040      // Badge the email address if there's an authentication error.
1041      if (HasAuthError(browser_->profile())) {
1042        const gfx::ImageSkia warning_image = *rb->GetImageNamed(
1043            IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia();
1044        auth_error_email_button_ =
1045            new RightAlignedIconLabelButton(this, avatar_item.sync_state);
1046        auth_error_email_button_->SetElideBehavior(gfx::ELIDE_EMAIL);
1047        auth_error_email_button_->SetBorder(views::Border::NullBorder());
1048        auth_error_email_button_->SetImage(
1049            views::LabelButton::STATE_NORMAL, warning_image);
1050        auth_error_email_button_->SetTextColor(
1051            views::LabelButton::STATE_NORMAL,
1052            views::Link::GetDefaultEnabledColor());
1053        layout->AddView(auth_error_email_button_);
1054      } else {
1055        views::Label* email_label = new views::Label(avatar_item.sync_state);
1056        email_label->SetElideBehavior(gfx::ELIDE_EMAIL);
1057        layout->AddView(email_label);
1058      }
1059    }
1060  } else {
1061    SigninManagerBase* signin_manager = SigninManagerFactory::GetForProfile(
1062        browser_->profile()->GetOriginalProfile());
1063    if (signin_manager->IsSigninAllowed()) {
1064      views::Label* promo = new views::Label(
1065          l10n_util::GetStringUTF16(IDS_PROFILES_SIGNIN_PROMO));
1066      promo->SetMultiLine(true);
1067      promo->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1068      layout->StartRowWithPadding(1, 0, 0,
1069                                  views::kRelatedControlSmallVerticalSpacing);
1070      layout->StartRow(1, 0);
1071      layout->AddView(promo);
1072
1073      signin_current_profile_link_ = new views::BlueButton(
1074        this, l10n_util::GetStringFUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL,
1075            l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)));
1076      layout->StartRowWithPadding(1, 0, 0,
1077                                  views::kRelatedControlVerticalSpacing);
1078      layout->StartRow(1, 0);
1079      layout->AddView(signin_current_profile_link_);
1080    }
1081  }
1082
1083  return view;
1084}
1085
1086views::View* ProfileChooserView::CreateGuestProfileView() {
1087  gfx::Image guest_icon =
1088      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
1089          profiles::GetPlaceholderAvatarIconResourceID());
1090  AvatarMenu::Item guest_avatar_item(0, 0, guest_icon);
1091  guest_avatar_item.active = true;
1092  guest_avatar_item.name = l10n_util::GetStringUTF16(
1093      IDS_PROFILES_GUEST_PROFILE_NAME);
1094  guest_avatar_item.signed_in = false;
1095
1096  return CreateCurrentProfileView(guest_avatar_item, true);
1097}
1098
1099views::View* ProfileChooserView::CreateOtherProfilesView(
1100    const Indexes& avatars_to_show) {
1101  views::View* view = new views::View();
1102  views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
1103
1104  int num_avatars_to_show = avatars_to_show.size();
1105  for (int i = 0; i < num_avatars_to_show; ++i) {
1106    const size_t index = avatars_to_show[i];
1107    const AvatarMenu::Item& item = avatar_menu_->GetItemAt(index);
1108    const int kSmallImageSide = 32;
1109
1110    gfx::Image image = profiles::GetSizedAvatarIcon(
1111        item.icon, true, kSmallImageSide, kSmallImageSide);
1112
1113    views::LabelButton* button = new BackgroundColorHoverButton(
1114        this,
1115        item.name,
1116        *image.ToImageSkia());
1117    open_other_profile_indexes_map_[button] = index;
1118
1119    layout->StartRow(1, 0);
1120    layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1121    layout->StartRow(1, 0);
1122    layout->AddView(button);
1123  }
1124
1125  return view;
1126}
1127
1128views::View* ProfileChooserView::CreateOptionsView(bool enable_lock) {
1129  views::View* view = new views::View();
1130  views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
1131
1132  base::string16 text = browser_->profile()->IsGuestSession() ?
1133      l10n_util::GetStringUTF16(IDS_PROFILES_EXIT_GUEST) :
1134      l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_USERS_BUTTON);
1135  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1136  users_button_ = new BackgroundColorHoverButton(
1137      this,
1138      text,
1139      *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
1140  layout->StartRow(1, 0);
1141  layout->AddView(users_button_);
1142
1143  if (ShouldShowGoIncognito()) {
1144    layout->StartRow(1, 0);
1145    layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1146
1147    // TODO(noms): Use the correct incognito icon when it's available.
1148    go_incognito_button_ = new BackgroundColorHoverButton(
1149        this,
1150        l10n_util::GetStringUTF16(IDS_PROFILES_GO_INCOGNITO_BUTTON),
1151        *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
1152    layout->StartRow(1, 0);
1153    layout->AddView(go_incognito_button_);
1154  }
1155
1156  if (enable_lock) {
1157    layout->StartRow(1, 0);
1158    layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1159
1160    lock_button_ = new BackgroundColorHoverButton(
1161        this,
1162        l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON),
1163        *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_LOCK));
1164    layout->StartRow(1, 0);
1165    layout->AddView(lock_button_);
1166  }
1167  return view;
1168}
1169
1170views::View* ProfileChooserView::CreateSupervisedUserDisclaimerView() {
1171  views::View* view = new views::View();
1172  views::GridLayout* layout = CreateSingleColumnLayout(
1173      view, kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew);
1174  layout->SetInsets(views::kRelatedControlVerticalSpacing,
1175                    views::kButtonHEdgeMarginNew,
1176                    views::kRelatedControlVerticalSpacing,
1177                    views::kButtonHEdgeMarginNew);
1178  views::Label* disclaimer = new views::Label(
1179      avatar_menu_->GetSupervisedUserInformation());
1180  disclaimer->SetMultiLine(true);
1181  disclaimer->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1182  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1183  disclaimer->SetFontList(rb->GetFontList(ui::ResourceBundle::SmallFont));
1184  layout->StartRow(1, 0);
1185  layout->AddView(disclaimer);
1186
1187  return view;
1188}
1189
1190views::View* ProfileChooserView::CreateCurrentProfileAccountsView(
1191    const AvatarMenu::Item& avatar_item) {
1192  DCHECK(avatar_item.signed_in);
1193  views::View* view = new views::View();
1194  view->set_background(views::Background::CreateSolidBackground(
1195      profiles::kAvatarBubbleAccountsBackgroundColor));
1196  views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
1197
1198  Profile* profile = browser_->profile();
1199  std::string primary_account =
1200      SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedUsername();
1201  DCHECK(!primary_account.empty());
1202  std::vector<std::string>accounts =
1203      profiles::GetSecondaryAccountsForProfile(profile, primary_account);
1204
1205  // Get state of authentication error, if any.
1206  std::string error_account_id = GetAuthErrorAccountId(profile);
1207
1208  // The primary account should always be listed first.
1209  // TODO(rogerta): we still need to further differentiate the primary account
1210  // from the others in the UI, so more work is likely required here:
1211  // crbug.com/311124.
1212  CreateAccountButton(layout, primary_account, true,
1213                      error_account_id == primary_account, kFixedMenuWidth);
1214  for (size_t i = 0; i < accounts.size(); ++i)
1215    CreateAccountButton(layout, accounts[i], false,
1216                        error_account_id == accounts[i], kFixedMenuWidth);
1217
1218  if (!profile->IsSupervised()) {
1219    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
1220
1221    add_account_link_ = CreateLink(l10n_util::GetStringFUTF16(
1222        IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, avatar_item.name), this);
1223    add_account_link_->SetBorder(views::Border::CreateEmptyBorder(
1224        0, views::kButtonVEdgeMarginNew,
1225        views::kRelatedControlVerticalSpacing, 0));
1226    layout->StartRow(1, 0);
1227    layout->AddView(add_account_link_);
1228  }
1229
1230  return view;
1231}
1232
1233void ProfileChooserView::CreateAccountButton(views::GridLayout* layout,
1234                                             const std::string& account,
1235                                             bool is_primary_account,
1236                                             bool reauth_required,
1237                                             int width) {
1238  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1239  const gfx::ImageSkia* delete_default_image =
1240      rb->GetImageNamed(IDR_CLOSE_1).ToImageSkia();
1241  const int kDeleteButtonWidth = delete_default_image->width();
1242  const gfx::ImageSkia warning_default_image = reauth_required ?
1243      *rb->GetImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia() :
1244      gfx::ImageSkia();
1245  const int kWarningButtonWidth = reauth_required ?
1246      warning_default_image.width() + views::kRelatedButtonHSpacing : 0;
1247  int available_width = width - 2 * views::kButtonHEdgeMarginNew
1248      - kDeleteButtonWidth - kWarningButtonWidth;
1249  views::LabelButton* email_button = new BackgroundColorHoverButton(
1250      reauth_required ? this : NULL,
1251      base::UTF8ToUTF16(account),
1252      warning_default_image);
1253  email_button->SetElideBehavior(gfx::ELIDE_EMAIL);
1254  email_button->SetMinSize(gfx::Size(0, kButtonHeight));
1255  email_button->SetMaxSize(gfx::Size(available_width, kButtonHeight));
1256  layout->StartRow(1, 0);
1257  layout->AddView(email_button);
1258
1259  if (reauth_required)
1260    reauth_account_button_map_[email_button] = account;
1261
1262  // Delete button.
1263  if (!browser_->profile()->IsSupervised()) {
1264    views::ImageButton* delete_button = new views::ImageButton(this);
1265    delete_button->SetImageAlignment(views::ImageButton::ALIGN_RIGHT,
1266                                     views::ImageButton::ALIGN_MIDDLE);
1267    delete_button->SetImage(views::ImageButton::STATE_NORMAL,
1268                            delete_default_image);
1269    delete_button->SetImage(views::ImageButton::STATE_HOVERED,
1270                            rb->GetImageSkiaNamed(IDR_CLOSE_1_H));
1271    delete_button->SetImage(views::ImageButton::STATE_PRESSED,
1272                            rb->GetImageSkiaNamed(IDR_CLOSE_1_P));
1273    delete_button->SetBounds(
1274        width - views::kButtonHEdgeMarginNew - kDeleteButtonWidth,
1275        0, kDeleteButtonWidth, kButtonHeight);
1276
1277    email_button->set_notify_enter_exit_on_child(true);
1278    email_button->AddChildView(delete_button);
1279
1280    // Save the original email address, as the button text could be elided.
1281    delete_account_button_map_[delete_button] = account;
1282  }
1283}
1284
1285views::View* ProfileChooserView::CreateGaiaSigninView() {
1286  GURL url;
1287  int message_id;
1288
1289  switch (view_mode_) {
1290    case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN:
1291      url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_SIGN_IN,
1292                                false /* auto_close */,
1293                                true /* is_constrained */);
1294      message_id = IDS_PROFILES_GAIA_SIGNIN_TITLE;
1295      break;
1296    case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT:
1297      url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT,
1298                                false /* auto_close */,
1299                                true /* is_constrained */);
1300      message_id = IDS_PROFILES_GAIA_ADD_ACCOUNT_TITLE;
1301      break;
1302    case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH: {
1303      DCHECK(HasAuthError(browser_->profile()));
1304      url = signin::GetReauthURL(browser_->profile(),
1305                                 GetAuthErrorUsername(browser_->profile()));
1306      message_id = IDS_PROFILES_GAIA_REAUTH_TITLE;
1307      break;
1308    }
1309    default:
1310      NOTREACHED() << "Called with invalid mode=" << view_mode_;
1311      return NULL;
1312  }
1313
1314  // Adds Gaia signin webview
1315  Profile* profile = browser_->profile();
1316  views::WebView* web_view = new views::WebView(profile);
1317  web_view->LoadInitialURL(url);
1318  web_view->SetPreferredSize(
1319      gfx::Size(kFixedGaiaViewWidth, kFixedGaiaViewHeight));
1320
1321  TitleCard* title_card = new TitleCard(l10n_util::GetStringUTF16(message_id),
1322                                        this,
1323                                        &gaia_signin_cancel_button_);
1324  return TitleCard::AddPaddedTitleCard(
1325      web_view, title_card, kFixedGaiaViewWidth);
1326}
1327
1328views::View* ProfileChooserView::CreateAccountRemovalView() {
1329  views::View* view = new views::View();
1330  views::GridLayout* layout = CreateSingleColumnLayout(
1331      view, kFixedAccountRemovalViewWidth - 2 * views::kButtonHEdgeMarginNew);
1332  layout->SetInsets(0,
1333                    views::kButtonHEdgeMarginNew,
1334                    views::kButtonVEdgeMarginNew,
1335                    views::kButtonHEdgeMarginNew);
1336
1337  const std::string& primary_account = SigninManagerFactory::GetForProfile(
1338      browser_->profile())->GetAuthenticatedUsername();
1339  bool is_primary_account = primary_account == account_id_to_remove_;
1340
1341  // Adds main text.
1342  layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing);
1343  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1344  const gfx::FontList& small_font_list =
1345      rb->GetFontList(ui::ResourceBundle::SmallFont);
1346
1347  if (is_primary_account) {
1348    std::vector<size_t> offsets;
1349    const base::string16 settings_text =
1350        l10n_util::GetStringUTF16(IDS_PROFILES_SETTINGS_LINK);
1351    const base::string16 primary_account_removal_text =
1352        l10n_util::GetStringFUTF16(IDS_PROFILES_PRIMARY_ACCOUNT_REMOVAL_TEXT,
1353            base::UTF8ToUTF16(account_id_to_remove_), settings_text, &offsets);
1354    views::StyledLabel* primary_account_removal_label =
1355        new views::StyledLabel(primary_account_removal_text, this);
1356    primary_account_removal_label->AddStyleRange(
1357        gfx::Range(offsets[1], offsets[1] + settings_text.size()),
1358        views::StyledLabel::RangeStyleInfo::CreateForLink());
1359    primary_account_removal_label->SetBaseFontList(small_font_list);
1360    layout->AddView(primary_account_removal_label);
1361  } else {
1362    views::Label* content_label = new views::Label(
1363        l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TEXT));
1364    content_label->SetMultiLine(true);
1365    content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1366    content_label->SetFontList(small_font_list);
1367    layout->AddView(content_label);
1368  }
1369
1370  // Adds button.
1371  if (!is_primary_account) {
1372    remove_account_button_ = new views::BlueButton(
1373        this, l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_BUTTON));
1374    remove_account_button_->SetHorizontalAlignment(
1375        gfx::ALIGN_CENTER);
1376    layout->StartRowWithPadding(
1377        1, 0, 0, views::kUnrelatedControlVerticalSpacing);
1378    layout->AddView(remove_account_button_);
1379  } else {
1380    layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
1381  }
1382
1383  TitleCard* title_card = new TitleCard(
1384      l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TITLE),
1385      this, &account_removal_cancel_button_);
1386  return TitleCard::AddPaddedTitleCard(view, title_card,
1387      kFixedAccountRemovalViewWidth);
1388}
1389
1390views::View* ProfileChooserView::CreateWelcomeUpgradeTutorialViewIfNeeded(
1391    bool tutorial_shown, const AvatarMenu::Item& avatar_item){
1392  if (first_run::IsChromeFirstRun())
1393    return NULL;
1394
1395  Profile* profile = browser_->profile();
1396  if (!avatar_item.signed_in) {
1397    profile->GetPrefs()->SetInteger(
1398        prefs::kProfileAvatarTutorialShown, kUpgradeWelcomeTutorialShowMax + 1);
1399    return NULL;
1400  }
1401
1402  const int show_count = profile->GetPrefs()->GetInteger(
1403      prefs::kProfileAvatarTutorialShown);
1404  // Do not show the tutorial if user has dismissed it.
1405  if (show_count > kUpgradeWelcomeTutorialShowMax)
1406    return NULL;
1407
1408  if (!tutorial_shown) {
1409    if (show_count == kUpgradeWelcomeTutorialShowMax)
1410      return NULL;
1411    profile->GetPrefs()->SetInteger(
1412        prefs::kProfileAvatarTutorialShown, show_count + 1);
1413  }
1414
1415  return CreateTutorialView(
1416      profiles::TUTORIAL_MODE_WELCOME_UPGRADE,
1417      l10n_util::GetStringFUTF16(
1418          IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE, avatar_item.name),
1419      l10n_util::GetStringUTF16(
1420          IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT),
1421      l10n_util::GetStringFUTF16(
1422          IDS_PROFILES_NOT_YOU, avatar_item.name),
1423      l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON),
1424      &tutorial_not_you_link_,
1425      &tutorial_see_whats_new_button_);
1426}
1427
1428views::View* ProfileChooserView::CreateSigninConfirmationView(){
1429  return CreateTutorialView(
1430      profiles::TUTORIAL_MODE_CONFIRM_SIGNIN,
1431      l10n_util::GetStringUTF16(IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE),
1432      l10n_util::GetStringUTF16(
1433          IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT),
1434      l10n_util::GetStringUTF16(IDS_PROFILES_SYNC_SETTINGS_LINK),
1435      l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_OK_BUTTON),
1436      &tutorial_sync_settings_link_,
1437      &tutorial_sync_settings_ok_button_);
1438}
1439
1440views::View* ProfileChooserView::CreateSwitchUserView(
1441    const AvatarMenu::Item& avatar_item) {
1442  views::View* view = new views::View();
1443  views::GridLayout* layout = CreateSingleColumnLayout(
1444      view, kFixedSwitchUserViewWidth);
1445  views::ColumnSet* columns = layout->AddColumnSet(1);
1446  columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
1447  int label_width =
1448      kFixedSwitchUserViewWidth - 2 * views::kButtonHEdgeMarginNew;
1449  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
1450                     views::GridLayout::FIXED, label_width, label_width);
1451  columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
1452
1453  // Adds main text.
1454  layout->StartRowWithPadding(1, 1, 0, views::kUnrelatedControlVerticalSpacing);
1455  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
1456  const gfx::FontList& small_font_list =
1457      rb->GetFontList(ui::ResourceBundle::SmallFont);
1458  views::Label* content_label = new views::Label(
1459      l10n_util::GetStringFUTF16(
1460          IDS_PROFILES_NOT_YOU_CONTENT_TEXT, avatar_item.name));
1461  content_label->SetMultiLine(true);
1462  content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1463  content_label->SetFontList(small_font_list);
1464  layout->AddView(content_label);
1465
1466  // Adds "Add person" button.
1467  layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing);
1468  layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1469
1470  add_person_button_ = new BackgroundColorHoverButton(
1471      this,
1472      l10n_util::GetStringUTF16(IDS_PROFILES_ADD_PERSON_BUTTON),
1473      *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
1474  layout->StartRow(1, 0);
1475  layout->AddView(add_person_button_);
1476
1477  // Adds "Disconnect your Google Account" button.
1478  layout->StartRow(1, 0);
1479  layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
1480
1481  disconnect_button_ = new BackgroundColorHoverButton(
1482      this,
1483      l10n_util::GetStringUTF16(IDS_PROFILES_DISCONNECT_BUTTON),
1484      *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR));
1485  layout->StartRow(1, 0);
1486  layout->AddView(disconnect_button_);
1487
1488  TitleCard* title_card = new TitleCard(
1489      l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatar_item.name),
1490      this, &switch_user_cancel_button_);
1491  return TitleCard::AddPaddedTitleCard(view, title_card,
1492      kFixedSwitchUserViewWidth);
1493}
1494
1495bool ProfileChooserView::ShouldShowGoIncognito() const {
1496  bool incognito_available =
1497      IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) !=
1498          IncognitoModePrefs::DISABLED;
1499  return incognito_available && !browser_->profile()->IsGuestSession();
1500}
1501
1502void ProfileChooserView::PostActionPerformed(
1503    ProfileMetrics::ProfileDesktopMenu action_performed) {
1504  ProfileMetrics::LogProfileDesktopMenu(action_performed, gaia_service_type_);
1505  gaia_service_type_ = signin::GAIA_SERVICE_TYPE_NONE;
1506}
1507