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