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