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
31namespace ash {
32namespace tray {
33
34namespace {
35
36const 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
48const 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.
62const int kTrayUserTileHoverBorderInset = 10;
63
64// Offsetting the popup message relative to the tray menu.
65const int kPopupMessageOffset = 25;
66
67// Switch to a user with the given |user_index|.
68void 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
83class 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
109class 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.
129class 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
157AddUserView::AddUserView(ButtonFromView* owner)
158    : add_user_(NULL), owner_(owner), anchor_(NULL) {
159  AddContent();
160  owner_->ForceBorderVisible(true);
161}
162
163AddUserView::~AddUserView() {
164  owner_->ForceBorderVisible(false);
165}
166
167gfx::Size AddUserView::GetPreferredSize() const {
168  return owner_->bounds().size();
169}
170
171void 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
201UserView::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
231UserView::~UserView() {
232  RemoveAddUserMenuOption();
233}
234
235void UserView::MouseMovedOutOfHost() {
236  RemoveAddUserMenuOption();
237}
238
239TrayUser::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
252gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
253  DCHECK(user_card_view_);
254  return user_card_view_->GetBoundsInScreen();
255}
256
257gfx::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
267int UserView::GetHeightForWidth(int width) const {
268  return GetPreferredSize().height();
269}
270
271void 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
317void 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
349void UserView::OnWillChangeFocus(View* focused_before, View* focused_now) {
350  if (focused_now)
351    RemoveAddUserMenuOption();
352}
353
354void UserView::OnDidChangeFocus(View* focused_before, View* focused_now) {
355  // Nothing to do here.
356}
357
358void 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
386void 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
462void 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
558void 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