Home | History | Annotate | Download | only in user
      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 "ash/system/user/user_view.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "ash/multi_profile_uma.h"
     10 #include "ash/popup_message.h"
     11 #include "ash/session/session_state_delegate.h"
     12 #include "ash/shell.h"
     13 #include "ash/shell_delegate.h"
     14 #include "ash/system/tray/system_tray.h"
     15 #include "ash/system/tray/system_tray_delegate.h"
     16 #include "ash/system/tray/tray_popup_label_button.h"
     17 #include "ash/system/tray/tray_popup_label_button_border.h"
     18 #include "ash/system/user/button_from_view.h"
     19 #include "ash/system/user/config.h"
     20 #include "ash/system/user/rounded_image_view.h"
     21 #include "ash/system/user/user_card_view.h"
     22 #include "components/user_manager/user_info.h"
     23 #include "grit/ash_resources.h"
     24 #include "grit/ash_strings.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 #include "ui/base/resource/resource_bundle.h"
     27 #include "ui/views/layout/fill_layout.h"
     28 #include "ui/views/painter.h"
     29 #include "ui/wm/core/shadow_types.h"
     30 
     31 namespace ash {
     32 namespace tray {
     33 
     34 namespace {
     35 
     36 const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
     37     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     38     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
     39     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
     40     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     41     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
     42     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
     43     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     44     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
     45     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
     46 };
     47 
     48 const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
     49     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     50     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     51     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     52     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     53     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
     54     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     55     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     56     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     57     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
     58 };
     59 
     60 // When a hover border is used, it is starting this many pixels before the icon
     61 // position.
     62 const int kTrayUserTileHoverBorderInset = 10;
     63 
     64 // Offsetting the popup message relative to the tray menu.
     65 const int kPopupMessageOffset = 25;
     66 
     67 // Switch to a user with the given |user_index|.
     68 void SwitchUser(ash::MultiProfileIndex user_index) {
     69   // Do not switch users when the log screen is presented.
     70   if (ash::Shell::GetInstance()
     71           ->session_state_delegate()
     72           ->IsUserSessionBlocked())
     73     return;
     74 
     75   DCHECK(user_index > 0);
     76   ash::SessionStateDelegate* delegate =
     77       ash::Shell::GetInstance()->session_state_delegate();
     78   ash::MultiProfileUMA::RecordSwitchActiveUser(
     79       ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
     80   delegate->SwitchActiveUser(delegate->GetUserInfo(user_index)->GetUserID());
     81 }
     82 
     83 class LogoutButton : public TrayPopupLabelButton {
     84  public:
     85   // If |placeholder| is true, button is used as placeholder. That means that
     86   // button is inactive and is not painted, but consume the same ammount of
     87   // space, as if it was painted.
     88   LogoutButton(views::ButtonListener* listener,
     89                const base::string16& text,
     90                bool placeholder)
     91       : TrayPopupLabelButton(listener, text), placeholder_(placeholder) {
     92     SetEnabled(!placeholder_);
     93   }
     94 
     95   virtual ~LogoutButton() {}
     96 
     97  private:
     98   virtual void Paint(gfx::Canvas* canvas,
     99                      const views::CullSet& cull_set) OVERRIDE {
    100     // Just skip paint if this button used as a placeholder.
    101     if (!placeholder_)
    102       TrayPopupLabelButton::Paint(canvas, cull_set);
    103   }
    104 
    105   bool placeholder_;
    106   DISALLOW_COPY_AND_ASSIGN(LogoutButton);
    107 };
    108 
    109 class UserViewMouseWatcherHost : public views::MouseWatcherHost {
    110  public:
    111   explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
    112       : screen_area_(screen_area) {}
    113   virtual ~UserViewMouseWatcherHost() {}
    114 
    115   // Implementation of MouseWatcherHost.
    116   virtual bool Contains(const gfx::Point& screen_point,
    117                         views::MouseWatcherHost::MouseEventType type) OVERRIDE {
    118     return screen_area_.Contains(screen_point);
    119   }
    120 
    121  private:
    122   gfx::Rect screen_area_;
    123 
    124   DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
    125 };
    126 
    127 // The menu item view which gets shown when the user clicks in multi profile
    128 // mode onto the user item.
    129 class AddUserView : public views::View {
    130  public:
    131   // The |owner| is the view for which this view gets created.
    132   AddUserView(ButtonFromView* owner);
    133   virtual ~AddUserView();
    134 
    135   // Get the anchor view for a message.
    136   views::View* anchor() { return anchor_; }
    137 
    138  private:
    139   // Overridden from views::View.
    140   virtual gfx::Size GetPreferredSize() const OVERRIDE;
    141 
    142   // Create the additional client content for this item.
    143   void AddContent();
    144 
    145   // This is the content we create and show.
    146   views::View* add_user_;
    147 
    148   // This is the owner view of this item.
    149   ButtonFromView* owner_;
    150 
    151   // The anchor view for targetted bubble messages.
    152   views::View* anchor_;
    153 
    154   DISALLOW_COPY_AND_ASSIGN(AddUserView);
    155 };
    156 
    157 AddUserView::AddUserView(ButtonFromView* owner)
    158     : add_user_(NULL), owner_(owner), anchor_(NULL) {
    159   AddContent();
    160   owner_->ForceBorderVisible(true);
    161 }
    162 
    163 AddUserView::~AddUserView() {
    164   owner_->ForceBorderVisible(false);
    165 }
    166 
    167 gfx::Size AddUserView::GetPreferredSize() const {
    168   return owner_->bounds().size();
    169 }
    170 
    171 void AddUserView::AddContent() {
    172   SetLayoutManager(new views::FillLayout());
    173   set_background(views::Background::CreateSolidBackground(kBackgroundColor));
    174 
    175   add_user_ = new views::View;
    176   add_user_->SetBorder(views::Border::CreateEmptyBorder(
    177       0, kTrayUserTileHoverBorderInset, 0, 0));
    178 
    179   add_user_->SetLayoutManager(new views::BoxLayout(
    180       views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
    181   AddChildViewAt(add_user_, 0);
    182 
    183   // Add the [+] icon which is also the anchor for messages.
    184   RoundedImageView* icon = new RoundedImageView(kTrayAvatarCornerRadius, true);
    185   anchor_ = icon;
    186   icon->SetImage(*ui::ResourceBundle::GetSharedInstance()
    187                       .GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER)
    188                       .ToImageSkia(),
    189                  gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
    190   add_user_->AddChildView(icon);
    191 
    192   // Add the command text.
    193   views::Label* command_label = new views::Label(
    194       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
    195   command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    196   add_user_->AddChildView(command_label);
    197 }
    198 
    199 }  // namespace
    200 
    201 UserView::UserView(SystemTrayItem* owner,
    202                    user::LoginStatus login,
    203                    MultiProfileIndex index,
    204                    bool for_detailed_view)
    205     : multiprofile_index_(index),
    206       user_card_view_(NULL),
    207       owner_(owner),
    208       is_user_card_button_(false),
    209       logout_button_(NULL),
    210       add_user_enabled_(true),
    211       for_detailed_view_(for_detailed_view),
    212       focus_manager_(NULL) {
    213   CHECK_NE(user::LOGGED_IN_NONE, login);
    214   if (!index) {
    215     // Only the logged in user will have a background. All other users will have
    216     // to allow the TrayPopupContainer highlighting the menu line.
    217     set_background(views::Background::CreateSolidBackground(
    218         login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor
    219                                         : kBackgroundColor));
    220   }
    221   SetLayoutManager(new views::BoxLayout(
    222       views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
    223   // The logout button must be added before the user card so that the user card
    224   // can correctly calculate the remaining available width.
    225   // Note that only the current multiprofile user gets a button.
    226   if (!multiprofile_index_)
    227     AddLogoutButton(login);
    228   AddUserCard(login);
    229 }
    230 
    231 UserView::~UserView() {
    232   RemoveAddUserMenuOption();
    233 }
    234 
    235 void UserView::MouseMovedOutOfHost() {
    236   RemoveAddUserMenuOption();
    237 }
    238 
    239 TrayUser::TestState UserView::GetStateForTest() const {
    240   if (add_menu_option_.get()) {
    241     return add_user_enabled_ ? TrayUser::ACTIVE : TrayUser::ACTIVE_BUT_DISABLED;
    242   }
    243 
    244   if (!is_user_card_button_)
    245     return TrayUser::SHOWN;
    246 
    247   return static_cast<ButtonFromView*>(user_card_view_)->is_hovered_for_test()
    248              ? TrayUser::HOVERED
    249              : TrayUser::SHOWN;
    250 }
    251 
    252 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
    253   DCHECK(user_card_view_);
    254   return user_card_view_->GetBoundsInScreen();
    255 }
    256 
    257 gfx::Size UserView::GetPreferredSize() const {
    258   gfx::Size size = views::View::GetPreferredSize();
    259   // Only the active user panel will be forced to a certain height.
    260   if (!multiprofile_index_) {
    261     size.set_height(
    262         std::max(size.height(), kTrayPopupItemHeight + GetInsets().height()));
    263   }
    264   return size;
    265 }
    266 
    267 int UserView::GetHeightForWidth(int width) const {
    268   return GetPreferredSize().height();
    269 }
    270 
    271 void UserView::Layout() {
    272   gfx::Rect contents_area(GetContentsBounds());
    273   if (user_card_view_ && logout_button_) {
    274     // Give the logout button the space it requests.
    275     gfx::Rect logout_area = contents_area;
    276     logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
    277     logout_area.set_x(contents_area.right() - logout_area.width());
    278 
    279     // Give the remaining space to the user card.
    280     gfx::Rect user_card_area = contents_area;
    281     int remaining_width = contents_area.width() - logout_area.width();
    282     if (IsMultiProfileSupportedAndUserActive() ||
    283         IsMultiAccountSupportedAndUserActive()) {
    284       // In multiprofile/multiaccount case |user_card_view_| and
    285       // |logout_button_| have to have the same height.
    286       int y = std::min(user_card_area.y(), logout_area.y());
    287       int height = std::max(user_card_area.height(), logout_area.height());
    288       logout_area.set_y(y);
    289       logout_area.set_height(height);
    290       user_card_area.set_y(y);
    291       user_card_area.set_height(height);
    292 
    293       // In multiprofile mode we have also to increase the size of the card by
    294       // the size of the border to make it overlap with the logout button.
    295       user_card_area.set_width(std::max(0, remaining_width + 1));
    296 
    297       // To make the logout button symmetrical with the user card we also make
    298       // the button longer by the same size the hover area in front of the icon
    299       // got inset.
    300       logout_area.set_width(logout_area.width() +
    301                             kTrayUserTileHoverBorderInset);
    302     } else {
    303       // In all other modes we have to make sure that there is enough spacing
    304       // between the two.
    305       remaining_width -= kTrayPopupPaddingBetweenItems;
    306     }
    307     user_card_area.set_width(remaining_width);
    308     user_card_view_->SetBoundsRect(user_card_area);
    309     logout_button_->SetBoundsRect(logout_area);
    310   } else if (user_card_view_) {
    311     user_card_view_->SetBoundsRect(contents_area);
    312   } else if (logout_button_) {
    313     logout_button_->SetBoundsRect(contents_area);
    314   }
    315 }
    316 
    317 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
    318   if (sender == logout_button_) {
    319     Shell::GetInstance()->metrics()->RecordUserMetricsAction(
    320         ash::UMA_STATUS_AREA_SIGN_OUT);
    321     RemoveAddUserMenuOption();
    322     Shell::GetInstance()->system_tray_delegate()->SignOut();
    323   } else if (sender == user_card_view_ && !multiprofile_index_ &&
    324              IsMultiAccountSupportedAndUserActive()) {
    325     owner_->TransitionDetailedView();
    326   } else if (sender == user_card_view_ &&
    327              IsMultiProfileSupportedAndUserActive()) {
    328     if (!multiprofile_index_) {
    329       ToggleAddUserMenuOption();
    330     } else {
    331       RemoveAddUserMenuOption();
    332       SwitchUser(multiprofile_index_);
    333       // Since the user list is about to change the system menu should get
    334       // closed.
    335       owner_->system_tray()->CloseSystemBubble();
    336     }
    337   } else if (add_menu_option_.get() &&
    338              sender == add_menu_option_->GetContentsView()) {
    339     RemoveAddUserMenuOption();
    340     // Let the user add another account to the session.
    341     MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
    342     Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
    343     owner_->system_tray()->CloseSystemBubble();
    344   } else {
    345     NOTREACHED();
    346   }
    347 }
    348 
    349 void UserView::OnWillChangeFocus(View* focused_before, View* focused_now) {
    350   if (focused_now)
    351     RemoveAddUserMenuOption();
    352 }
    353 
    354 void UserView::OnDidChangeFocus(View* focused_before, View* focused_now) {
    355   // Nothing to do here.
    356 }
    357 
    358 void UserView::AddLogoutButton(user::LoginStatus login) {
    359   const base::string16 title =
    360       user::GetLocalizedSignOutStringForStatus(login, true);
    361   TrayPopupLabelButton* logout_button =
    362       new LogoutButton(this, title, for_detailed_view_);
    363   logout_button->SetAccessibleName(title);
    364   logout_button_ = logout_button;
    365   // In public account mode, the logout button border has a custom color.
    366   if (login == user::LOGGED_IN_PUBLIC) {
    367     scoped_ptr<TrayPopupLabelButtonBorder> border(
    368         new TrayPopupLabelButtonBorder());
    369     border->SetPainter(false,
    370                        views::Button::STATE_NORMAL,
    371                        views::Painter::CreateImageGridPainter(
    372                            kPublicAccountLogoutButtonBorderImagesNormal));
    373     border->SetPainter(false,
    374                        views::Button::STATE_HOVERED,
    375                        views::Painter::CreateImageGridPainter(
    376                            kPublicAccountLogoutButtonBorderImagesHovered));
    377     border->SetPainter(false,
    378                        views::Button::STATE_PRESSED,
    379                        views::Painter::CreateImageGridPainter(
    380                            kPublicAccountLogoutButtonBorderImagesHovered));
    381     logout_button_->SetBorder(border.PassAs<views::Border>());
    382   }
    383   AddChildView(logout_button_);
    384 }
    385 
    386 void UserView::AddUserCard(user::LoginStatus login) {
    387   // Add padding around the panel.
    388   SetBorder(views::Border::CreateEmptyBorder(kTrayPopupUserCardVerticalPadding,
    389                                              kTrayPopupPaddingHorizontal,
    390                                              kTrayPopupUserCardVerticalPadding,
    391                                              kTrayPopupPaddingHorizontal));
    392 
    393   views::TrayBubbleView* bubble_view =
    394       owner_->system_tray()->GetSystemBubble()->bubble_view();
    395   int max_card_width =
    396       bubble_view->GetMaximumSize().width() -
    397       (2 * kTrayPopupPaddingHorizontal + kTrayPopupPaddingBetweenItems);
    398   if (logout_button_)
    399     max_card_width -= logout_button_->GetPreferredSize().width();
    400   user_card_view_ =
    401       new UserCardView(login, max_card_width, multiprofile_index_);
    402   // The entry is clickable when no system modal dialog is open and one of the
    403   // multi user options is active.
    404   bool clickable = !Shell::GetInstance()->IsSystemModalWindowOpen() &&
    405                    (IsMultiProfileSupportedAndUserActive() ||
    406                     IsMultiAccountSupportedAndUserActive());
    407   if (clickable) {
    408     // To allow the border to start before the icon, reduce the size before and
    409     // add an inset to the icon to get the spacing.
    410     if (!multiprofile_index_) {
    411       SetBorder(views::Border::CreateEmptyBorder(
    412           kTrayPopupUserCardVerticalPadding,
    413           kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
    414           kTrayPopupUserCardVerticalPadding,
    415           kTrayPopupPaddingHorizontal));
    416       user_card_view_->SetBorder(views::Border::CreateEmptyBorder(
    417           0, kTrayUserTileHoverBorderInset, 0, 0));
    418     }
    419     gfx::Insets insets = gfx::Insets(1, 1, 1, 1);
    420     views::View* contents_view = user_card_view_;
    421     ButtonFromView* button = NULL;
    422     if (!for_detailed_view_) {
    423       if (multiprofile_index_) {
    424         // Since the activation border needs to be drawn around the tile, we
    425         // have to put the tile into another view which fills the menu panel,
    426         // but keeping the offsets of the content.
    427         contents_view = new views::View();
    428         contents_view->SetBorder(views::Border::CreateEmptyBorder(
    429             kTrayPopupUserCardVerticalPadding,
    430             kTrayPopupPaddingHorizontal,
    431             kTrayPopupUserCardVerticalPadding,
    432             kTrayPopupPaddingHorizontal));
    433         contents_view->SetLayoutManager(new views::FillLayout());
    434         SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 0));
    435         contents_view->AddChildView(user_card_view_);
    436         insets = gfx::Insets(1, 1, 1, 3);
    437       }
    438       button = new ButtonFromView(contents_view,
    439                                   this,
    440                                   !multiprofile_index_,
    441                                   insets);
    442       // TODO(skuhne): For accessibility we need to call |SetAccessibleName|
    443       // with a useful name (string freeze for M37 has passed).
    444     } else {
    445       // We want user card for detailed view to have exactly the same look
    446       // as user card for default view. That's why we wrap it in a button
    447       // without click listener and special hover behavior.
    448       button = new ButtonFromView(contents_view, NULL, false, insets);
    449     }
    450     // A click on the button should not trigger a focus change.
    451     button->set_request_focus_on_press(false);
    452     user_card_view_ = button;
    453     is_user_card_button_ = true;
    454   }
    455   AddChildViewAt(user_card_view_, 0);
    456   // Card for supervised user can consume more space than currently
    457   // available. In that case we should increase system bubble's width.
    458   if (login == user::LOGGED_IN_PUBLIC)
    459     bubble_view->SetWidth(GetPreferredSize().width());
    460 }
    461 
    462 void UserView::ToggleAddUserMenuOption() {
    463   if (add_menu_option_.get()) {
    464     RemoveAddUserMenuOption();
    465     return;
    466   }
    467 
    468   // Note: We do not need to install a global event handler to delete this
    469   // item since it will destroyed automatically before the menu / user menu item
    470   // gets destroyed..
    471   add_menu_option_.reset(new views::Widget);
    472   views::Widget::InitParams params;
    473   params.type = views::Widget::InitParams::TYPE_TOOLTIP;
    474   params.keep_on_top = true;
    475   params.context = this->GetWidget()->GetNativeWindow();
    476   params.accept_events = true;
    477   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    478   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
    479   add_menu_option_->Init(params);
    480   add_menu_option_->SetOpacity(0xFF);
    481   add_menu_option_->GetNativeWindow()->set_owned_by_parent(false);
    482   SetShadowType(add_menu_option_->GetNativeView(), wm::SHADOW_TYPE_NONE);
    483 
    484   // Position it below our user card.
    485   gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
    486   bounds.set_y(bounds.y() + bounds.height());
    487   add_menu_option_->SetBounds(bounds);
    488 
    489   // Show the content.
    490   add_menu_option_->SetAlwaysOnTop(true);
    491   add_menu_option_->Show();
    492 
    493   AddUserView* add_user_view =
    494       new AddUserView(static_cast<ButtonFromView*>(user_card_view_));
    495 
    496   const SessionStateDelegate* delegate =
    497       Shell::GetInstance()->session_state_delegate();
    498 
    499   SessionStateDelegate::AddUserError add_user_error;
    500   add_user_enabled_ = delegate->CanAddUserToMultiProfile(&add_user_error);
    501 
    502   ButtonFromView* button = new ButtonFromView(add_user_view,
    503                                               add_user_enabled_ ? this : NULL,
    504                                               add_user_enabled_,
    505                                               gfx::Insets(1, 1, 1, 1));
    506   button->set_request_focus_on_press(false);
    507   button->SetAccessibleName(
    508       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
    509   button->ForceBorderVisible(true);
    510   add_menu_option_->SetContentsView(button);
    511 
    512   if (add_user_enabled_) {
    513     // We activate the entry automatically if invoked with focus.
    514     if (user_card_view_->HasFocus()) {
    515       button->GetFocusManager()->SetFocusedView(button);
    516       user_card_view_->GetFocusManager()->SetFocusedView(button);
    517     }
    518   } else {
    519     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    520     int message_id = 0;
    521     switch (add_user_error) {
    522       case SessionStateDelegate::ADD_USER_ERROR_NOT_ALLOWED_PRIMARY_USER:
    523         message_id = IDS_ASH_STATUS_TRAY_MESSAGE_NOT_ALLOWED_PRIMARY_USER;
    524         break;
    525       case SessionStateDelegate::ADD_USER_ERROR_MAXIMUM_USERS_REACHED:
    526         message_id = IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER;
    527         break;
    528       case SessionStateDelegate::ADD_USER_ERROR_OUT_OF_USERS:
    529         message_id = IDS_ASH_STATUS_TRAY_MESSAGE_OUT_OF_USERS;
    530         break;
    531       default:
    532         NOTREACHED() << "Unknown adding user error " << add_user_error;
    533     }
    534 
    535     popup_message_.reset(new PopupMessage(
    536         bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
    537         bundle.GetLocalizedString(message_id),
    538         PopupMessage::ICON_WARNING,
    539         add_user_view->anchor(),
    540         views::BubbleBorder::TOP_LEFT,
    541         gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
    542         2 * kPopupMessageOffset));
    543   }
    544   // Find the screen area which encloses both elements and sets then a mouse
    545   // watcher which will close the "menu".
    546   gfx::Rect area = user_card_view_->GetBoundsInScreen();
    547   area.set_height(2 * area.height());
    548   mouse_watcher_.reset(
    549       new views::MouseWatcher(new UserViewMouseWatcherHost(area), this));
    550   mouse_watcher_->Start();
    551   // Install a listener to focus changes so that we can remove the card when
    552   // the focus gets changed. When called through the destruction of the bubble,
    553   // the FocusManager cannot be determined anymore and we remember it here.
    554   focus_manager_ = user_card_view_->GetFocusManager();
    555   focus_manager_->AddFocusChangeListener(this);
    556 }
    557 
    558 void UserView::RemoveAddUserMenuOption() {
    559   if (!add_menu_option_.get())
    560     return;
    561   focus_manager_->RemoveFocusChangeListener(this);
    562   focus_manager_ = NULL;
    563   if (user_card_view_->GetFocusManager())
    564     user_card_view_->GetFocusManager()->ClearFocus();
    565   popup_message_.reset();
    566   mouse_watcher_.reset();
    567   add_menu_option_.reset();
    568 }
    569 
    570 }  // namespace tray
    571 }  // namespace ash
    572