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