Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / ash / system / user / tray_user.cc
1 // Copyright (c) 2012 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/tray_user.h"
6
7 #include <algorithm>
8 #include <climits>
9 #include <vector>
10
11 #include "ash/ash_switches.h"
12 #include "ash/metrics/user_metrics_recorder.h"
13 #include "ash/multi_profile_uma.h"
14 #include "ash/popup_message.h"
15 #include "ash/root_window_controller.h"
16 #include "ash/session_state_delegate.h"
17 #include "ash/shelf/shelf_layout_manager.h"
18 #include "ash/shell.h"
19 #include "ash/shell_delegate.h"
20 #include "ash/system/tray/system_tray.h"
21 #include "ash/system/tray/system_tray_delegate.h"
22 #include "ash/system/tray/system_tray_notifier.h"
23 #include "ash/system/tray/tray_constants.h"
24 #include "ash/system/tray/tray_item_view.h"
25 #include "ash/system/tray/tray_popup_label_button.h"
26 #include "ash/system/tray/tray_popup_label_button_border.h"
27 #include "ash/system/tray/tray_utils.h"
28 #include "base/i18n/rtl.h"
29 #include "base/logging.h"
30 #include "base/memory/scoped_vector.h"
31 #include "base/strings/string16.h"
32 #include "base/strings/string_util.h"
33 #include "base/strings/utf_string_conversions.h"
34 #include "grit/ash_resources.h"
35 #include "grit/ash_strings.h"
36 #include "skia/ext/image_operations.h"
37 #include "third_party/skia/include/core/SkCanvas.h"
38 #include "third_party/skia/include/core/SkPaint.h"
39 #include "third_party/skia/include/core/SkPath.h"
40 #include "ui/aura/window.h"
41 #include "ui/base/l10n/l10n_util.h"
42 #include "ui/base/resource/resource_bundle.h"
43 #include "ui/gfx/canvas.h"
44 #include "ui/gfx/font_list.h"
45 #include "ui/gfx/image/image.h"
46 #include "ui/gfx/image/image_skia_operations.h"
47 #include "ui/gfx/insets.h"
48 #include "ui/gfx/range/range.h"
49 #include "ui/gfx/rect.h"
50 #include "ui/gfx/render_text.h"
51 #include "ui/gfx/size.h"
52 #include "ui/gfx/skia_util.h"
53 #include "ui/gfx/text_elider.h"
54 #include "ui/gfx/text_utils.h"
55 #include "ui/views/border.h"
56 #include "ui/views/bubble/tray_bubble_view.h"
57 #include "ui/views/controls/button/button.h"
58 #include "ui/views/controls/button/custom_button.h"
59 #include "ui/views/controls/image_view.h"
60 #include "ui/views/controls/label.h"
61 #include "ui/views/controls/link.h"
62 #include "ui/views/controls/link_listener.h"
63 #include "ui/views/corewm/shadow_types.h"
64 #include "ui/views/layout/box_layout.h"
65 #include "ui/views/layout/fill_layout.h"
66 #include "ui/views/mouse_watcher.h"
67 #include "ui/views/painter.h"
68 #include "ui/views/view.h"
69 #include "ui/views/widget/widget.h"
70
71 namespace {
72
73 const int kUserDetailsVerticalPadding = 5;
74 const int kUserCardVerticalPadding = 10;
75 const int kProfileRoundedCornerRadius = 2;
76 const int kUserIconSize = 27;
77 const int kUserIconLargeSize = 32;
78 const int kUserIconLargeCornerRadius = 2;
79 const int kUserLabelToIconPadding = 5;
80 // When using multi login, this spacing is added between user icons.
81 const int kTrayLabelSpacing = 1;
82
83 // When a hover border is used, it is starting this many pixels before the icon
84 // position.
85 const int kTrayUserTileHoverBorderInset = 10;
86
87 // The border color of the user button.
88 const SkColor kBorderColor = 0xffdcdcdc;
89
90 // The invisible word joiner character, used as a marker to indicate the start
91 // and end of the user's display name in the public account user card's text.
92 const base::char16 kDisplayNameMark[] = { 0x2060, 0 };
93
94 const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
95     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
96     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
97     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
98     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
99     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
100     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
101     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
102     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
103     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
104 };
105
106 const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
107     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
108     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
109     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
110     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
111     IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
112     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
113     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
114     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
115     IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
116 };
117
118 // Offsetting the popup message relative to the tray menu.
119 const int kPopupMessageOffset = 25;
120
121 // Switch to a user with the given |user_index|.
122 void SwitchUser(ash::MultiProfileIndex user_index) {
123   // Do not switch users when the log screen is presented.
124   if (ash::Shell::GetInstance()->session_state_delegate()->
125           IsUserSessionBlocked())
126     return;
127
128   DCHECK(user_index > 0);
129   ash::SessionStateDelegate* delegate =
130       ash::Shell::GetInstance()->session_state_delegate();
131   ash::MultiProfileUMA::RecordSwitchActiveUser(
132       ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
133   delegate->SwitchActiveUser(delegate->GetUserID(user_index));
134 }
135
136 }  // namespace
137
138 namespace ash {
139 namespace internal {
140
141 namespace tray {
142
143 // A custom image view with rounded edges.
144 class RoundedImageView : public views::View {
145  public:
146   // Constructs a new rounded image view with rounded corners of radius
147   // |corner_radius|. If |active_user| is set, the icon will be drawn in
148   // full colors - otherwise it will fade into the background.
149   RoundedImageView(int corner_radius, bool active_user);
150   virtual ~RoundedImageView();
151
152   // Set the image that should be displayed. The image contents is copied to the
153   // receiver's image.
154   void SetImage(const gfx::ImageSkia& img, const gfx::Size& size);
155
156   // Set the radii of the corners independently.
157   void SetCornerRadii(int top_left,
158                       int top_right,
159                       int bottom_right,
160                       int bottom_left);
161
162  private:
163   // Overridden from views::View.
164   virtual gfx::Size GetPreferredSize() OVERRIDE;
165   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
166
167   gfx::ImageSkia image_;
168   gfx::ImageSkia resized_;
169   gfx::Size image_size_;
170   int corner_radius_[4];
171
172   // True if the given user is the active user and the icon should get
173   // painted as active.
174   bool active_user_;
175
176   DISALLOW_COPY_AND_ASSIGN(RoundedImageView);
177 };
178
179 // An inactive user view which can be clicked to make active. Note that this
180 // "button" does not show as a button any click or hover changes.
181 class UserSwitcherView : public RoundedImageView {
182  public:
183   UserSwitcherView(int corner_radius, MultiProfileIndex user_index);
184   virtual ~UserSwitcherView() {}
185
186   virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
187   virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
188
189  private:
190   // The user index to activate when the item was clicked. Note that this
191   // index refers to the LRU list of logged in users.
192   MultiProfileIndex user_index_;
193
194   DISALLOW_COPY_AND_ASSIGN(UserSwitcherView);
195 };
196
197 // The user details shown in public account mode. This is essentially a label
198 // but with custom painting code as the text is styled with multiple colors and
199 // contains a link.
200 class PublicAccountUserDetails : public views::View,
201                                  public views::LinkListener {
202  public:
203   PublicAccountUserDetails(SystemTrayItem* owner, int used_width);
204   virtual ~PublicAccountUserDetails();
205
206  private:
207   // Overridden from views::View.
208   virtual void Layout() OVERRIDE;
209   virtual gfx::Size GetPreferredSize() OVERRIDE;
210   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
211
212   // Overridden from views::LinkListener.
213   virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
214
215   // Calculate a preferred size that ensures the label text and the following
216   // link do not wrap over more than three lines in total for aesthetic reasons
217   // if possible.
218   void CalculatePreferredSize(SystemTrayItem* owner, int used_width);
219
220   base::string16 text_;
221   views::Link* learn_more_;
222   gfx::Size preferred_size_;
223   ScopedVector<gfx::RenderText> lines_;
224
225   DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
226 };
227
228 // The button which holds the user card in case of multi profile.
229 class UserCard : public views::CustomButton {
230  public:
231   UserCard(views::ButtonListener* listener, bool active_user);
232   virtual ~UserCard();
233
234   // Called when the border should remain even in the non highlighted state.
235   void ForceBorderVisible(bool show);
236
237   // Overridden from views::View
238   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
239   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
240
241   // Check if the item is hovered.
242   bool is_hovered_for_test() {return button_hovered_; }
243
244  private:
245   // Change the hover/active state of the "button" when the status changes.
246   void ShowActive();
247
248   // True if this is the active user.
249   bool is_active_user_;
250
251   // True if button is hovered.
252   bool button_hovered_;
253
254   // True if the border should be visible.
255   bool show_border_;
256
257   DISALLOW_COPY_AND_ASSIGN(UserCard);
258 };
259
260 class UserViewMouseWatcherHost : public views::MouseWatcherHost {
261 public:
262  explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
263      : screen_area_(screen_area) {}
264  virtual ~UserViewMouseWatcherHost() {}
265
266  // Implementation of MouseWatcherHost.
267  virtual bool Contains(const gfx::Point& screen_point,
268                        views::MouseWatcherHost::MouseEventType type) OVERRIDE {
269    return screen_area_.Contains(screen_point);
270  }
271
272 private:
273  gfx::Rect screen_area_;
274
275  DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
276 };
277
278 // The view of a user item.
279 class UserView : public views::View,
280                  public views::ButtonListener,
281                  public views::MouseWatcherListener {
282  public:
283   UserView(SystemTrayItem* owner,
284            ash::user::LoginStatus login,
285            MultiProfileIndex index);
286   virtual ~UserView();
287
288   // Overridden from MouseWatcherListener:
289   virtual void MouseMovedOutOfHost() OVERRIDE;
290
291   TrayUser::TestState GetStateForTest() const;
292   gfx::Rect GetBoundsInScreenOfUserButtonForTest();
293
294  private:
295   // Overridden from views::View.
296   virtual gfx::Size GetPreferredSize() OVERRIDE;
297   virtual int GetHeightForWidth(int width) OVERRIDE;
298   virtual void Layout() OVERRIDE;
299
300   // Overridden from views::ButtonListener.
301   virtual void ButtonPressed(views::Button* sender,
302                              const ui::Event& event) OVERRIDE;
303
304   void AddLogoutButton(user::LoginStatus login);
305   void AddUserCard(SystemTrayItem* owner, user::LoginStatus login);
306
307   // Create a user icon representation for the user card.
308   views::View* CreateIconForUserCard(user::LoginStatus login);
309
310   // Create the additional user card content for the retail logged in mode.
311   void AddLoggedInRetailModeUserCardContent();
312
313   // Create the additional user card content for the public mode.
314   void AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner);
315
316   // Create the menu option to add another user. If |disabled| is set the user
317   // cannot actively click on the item.
318   void ToggleAddUserMenuOption();
319
320   // Returns true when multi profile is supported.
321   bool SupportsMultiProfile();
322
323   MultiProfileIndex multiprofile_index_;
324   // The view of the user card.
325   views::View* user_card_view_;
326
327   // This is the owner system tray item of this view.
328   SystemTrayItem* owner_;
329
330   // True if |user_card_view_| is a |UserView| - otherwise it is only a
331   // |views::View|.
332   bool is_user_card_;
333   views::View* logout_button_;
334   scoped_ptr<PopupMessage> popup_message_;
335   scoped_ptr<views::Widget> add_menu_option_;
336
337   // True when the add user panel is visible but not activatable.
338   bool add_user_visible_but_disabled_;
339
340   // The mouse watcher which takes care of out of window hover events.
341   scoped_ptr<views::MouseWatcher> mouse_watcher_;
342
343   DISALLOW_COPY_AND_ASSIGN(UserView);
344 };
345
346 // The menu item view which gets shown when the user clicks in multi profile
347 // mode onto the user item.
348 class AddUserView : public views::CustomButton,
349                     public views::ButtonListener {
350  public:
351   // The |owner| is the view for which this view gets created. The |listener|
352   // will get notified when this item gets clicked.
353   AddUserView(UserCard* owner, views::ButtonListener* listener);
354   virtual ~AddUserView();
355
356   // Get the anchor view for a message.
357   views::View* anchor() { return anchor_; }
358
359   // Overridden from views::ButtonListener.
360   virtual void ButtonPressed(views::Button* sender,
361                              const ui::Event& event) OVERRIDE;
362
363  private:
364   // Overridden from views::View.
365   virtual gfx::Size GetPreferredSize() OVERRIDE;
366   virtual int GetHeightForWidth(int width) OVERRIDE;
367   virtual void Layout() OVERRIDE;
368
369   // Create the additional client content for this item.
370   void AddContent();
371
372   // This is the content we create and show.
373   views::View* add_user_;
374
375   // This listener will get informed when someone clicks on this button.
376   views::ButtonListener* listener_;
377
378   // This is the owner view of this item.
379   UserCard* owner_;
380
381   // The anchor view for targetted bubble messages.
382   views::View* anchor_;
383
384   DISALLOW_COPY_AND_ASSIGN(AddUserView);
385 };
386
387 RoundedImageView::RoundedImageView(int corner_radius, bool active_user)
388     : active_user_(active_user) {
389   for (int i = 0; i < 4; ++i)
390     corner_radius_[i] = corner_radius;
391 }
392
393 RoundedImageView::~RoundedImageView() {}
394
395 void RoundedImageView::SetImage(const gfx::ImageSkia& img,
396                                 const gfx::Size& size) {
397   image_ = img;
398   image_size_ = size;
399
400   // Try to get the best image quality for the avatar.
401   resized_ = gfx::ImageSkiaOperations::CreateResizedImage(image_,
402       skia::ImageOperations::RESIZE_BEST, size);
403   if (GetWidget() && visible()) {
404     PreferredSizeChanged();
405     SchedulePaint();
406   }
407 }
408
409 void RoundedImageView::SetCornerRadii(int top_left,
410                                       int top_right,
411                                       int bottom_right,
412                                       int bottom_left) {
413   corner_radius_[0] = top_left;
414   corner_radius_[1] = top_right;
415   corner_radius_[2] = bottom_right;
416   corner_radius_[3] = bottom_left;
417 }
418
419 gfx::Size RoundedImageView::GetPreferredSize() {
420   return gfx::Size(image_size_.width() + GetInsets().width(),
421                    image_size_.height() + GetInsets().height());
422 }
423
424 void RoundedImageView::OnPaint(gfx::Canvas* canvas) {
425   View::OnPaint(canvas);
426   gfx::Rect image_bounds(size());
427   image_bounds.ClampToCenteredSize(GetPreferredSize());
428   image_bounds.Inset(GetInsets());
429   const SkScalar kRadius[8] = {
430     SkIntToScalar(corner_radius_[0]),
431     SkIntToScalar(corner_radius_[0]),
432     SkIntToScalar(corner_radius_[1]),
433     SkIntToScalar(corner_radius_[1]),
434     SkIntToScalar(corner_radius_[2]),
435     SkIntToScalar(corner_radius_[2]),
436     SkIntToScalar(corner_radius_[3]),
437     SkIntToScalar(corner_radius_[3])
438   };
439   SkPath path;
440   path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius);
441   SkPaint paint;
442   paint.setAntiAlias(true);
443   paint.setXfermodeMode(active_user_ ? SkXfermode::kSrcOver_Mode :
444                                        SkXfermode::kLuminosity_Mode);
445   canvas->DrawImageInPath(resized_, image_bounds.x(), image_bounds.y(),
446                           path, paint);
447 }
448
449 UserSwitcherView::UserSwitcherView(int corner_radius,
450                                    MultiProfileIndex user_index)
451     : RoundedImageView(corner_radius, false),
452       user_index_(user_index) {
453   SetEnabled(true);
454 }
455
456 void UserSwitcherView::OnMouseEvent(ui::MouseEvent* event) {
457   if (event->type() == ui::ET_MOUSE_PRESSED) {
458     SwitchUser(user_index_);
459     event->SetHandled();
460   }
461 }
462
463 void UserSwitcherView::OnTouchEvent(ui::TouchEvent* event) {
464   if (event->type() == ui::ET_TOUCH_PRESSED) {
465     SwitchUser(user_index_);
466     event->SetHandled();
467   }
468 }
469
470 PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner,
471                                                    int used_width)
472     : learn_more_(NULL) {
473   const int inner_padding =
474       kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
475   const bool rtl = base::i18n::IsRTL();
476   SetBorder(views::Border::CreateEmptyBorder(kUserDetailsVerticalPadding,
477                                              rtl ? 0 : inner_padding,
478                                              kUserDetailsVerticalPadding,
479                                              rtl ? inner_padding : 0));
480
481   // Retrieve the user's display name and wrap it with markers.
482   // Note that since this is a public account it always has to be the primary
483   // user.
484   base::string16 display_name =
485       Shell::GetInstance()->session_state_delegate()->GetUserDisplayName(0);
486   base::RemoveChars(display_name, kDisplayNameMark, &display_name);
487   display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0];
488   // Retrieve the domain managing the device and wrap it with markers.
489   base::string16 domain = base::UTF8ToUTF16(
490       Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain());
491   base::RemoveChars(domain, kDisplayNameMark, &domain);
492   base::i18n::WrapStringWithLTRFormatting(&domain);
493   // Retrieve the label text, inserting the display name and domain.
494   text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL,
495                                      display_name, domain);
496
497   learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
498   learn_more_->SetUnderline(false);
499   learn_more_->set_listener(this);
500   AddChildView(learn_more_);
501
502   CalculatePreferredSize(owner, used_width);
503 }
504
505 PublicAccountUserDetails::~PublicAccountUserDetails() {}
506
507 void PublicAccountUserDetails::Layout() {
508   lines_.clear();
509   const gfx::Rect contents_area = GetContentsBounds();
510   if (contents_area.IsEmpty())
511     return;
512
513   // Word-wrap the label text.
514   const gfx::FontList font_list;
515   std::vector<base::string16> lines;
516   gfx::ElideRectangleText(text_, font_list, contents_area.width(),
517                           contents_area.height(), gfx::ELIDE_LONG_WORDS,
518                           &lines);
519   // Loop through the lines, creating a renderer for each.
520   gfx::Point position = contents_area.origin();
521   gfx::Range display_name(gfx::Range::InvalidRange());
522   for (std::vector<base::string16>::const_iterator it = lines.begin();
523        it != lines.end(); ++it) {
524     gfx::RenderText* line = gfx::RenderText::CreateInstance();
525     line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
526     line->SetText(*it);
527     const gfx::Size size(contents_area.width(), line->GetStringSize().height());
528     line->SetDisplayRect(gfx::Rect(position, size));
529     position.set_y(position.y() + size.height());
530
531     // Set the default text color for the line.
532     line->SetColor(kPublicAccountUserCardTextColor);
533
534     // If a range of the line contains the user's display name, apply a custom
535     // text color to it.
536     if (display_name.is_empty())
537       display_name.set_start(it->find(kDisplayNameMark));
538     if (!display_name.is_empty()) {
539       display_name.set_end(
540           it->find(kDisplayNameMark, display_name.start() + 1));
541       gfx::Range line_range(0, it->size());
542       line->ApplyColor(kPublicAccountUserCardNameColor,
543                        display_name.Intersect(line_range));
544       // Update the range for the next line.
545       if (display_name.end() >= line_range.end())
546         display_name.set_start(0);
547       else
548         display_name = gfx::Range::InvalidRange();
549     }
550
551     lines_.push_back(line);
552   }
553
554   // Position the link after the label text, separated by a space. If it does
555   // not fit onto the last line of the text, wrap the link onto its own line.
556   const gfx::Size last_line_size = lines_.back()->GetStringSize();
557   const int space_width =
558       gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list);
559   const gfx::Size link_size = learn_more_->GetPreferredSize();
560   if (contents_area.width() - last_line_size.width() >=
561       space_width + link_size.width()) {
562     position.set_x(position.x() + last_line_size.width() + space_width);
563     position.set_y(position.y() - last_line_size.height());
564   }
565   position.set_y(position.y() - learn_more_->GetInsets().top());
566   gfx::Rect learn_more_bounds(position, link_size);
567   learn_more_bounds.Intersect(contents_area);
568   if (base::i18n::IsRTL()) {
569     const gfx::Insets insets = GetInsets();
570     learn_more_bounds.Offset(insets.right() - insets.left(), 0);
571   }
572   learn_more_->SetBoundsRect(learn_more_bounds);
573 }
574
575 gfx::Size PublicAccountUserDetails::GetPreferredSize() {
576   return preferred_size_;
577 }
578
579 void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) {
580   for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin();
581        it != lines_.end(); ++it) {
582     (*it)->Draw(canvas);
583   }
584   views::View::OnPaint(canvas);
585 }
586
587 void PublicAccountUserDetails::LinkClicked(views::Link* source,
588                                            int event_flags) {
589   DCHECK_EQ(source, learn_more_);
590   Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo();
591 }
592
593 void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner,
594                                                       int used_width) {
595   const gfx::FontList font_list;
596   const gfx::Size link_size = learn_more_->GetPreferredSize();
597   const int space_width =
598       gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list);
599   const gfx::Insets insets = GetInsets();
600   views::TrayBubbleView* bubble_view =
601       owner->system_tray()->GetSystemBubble()->bubble_view();
602   int min_width = std::max(
603       link_size.width(),
604       bubble_view->GetPreferredSize().width() - (used_width + insets.width()));
605   int max_width = std::min(
606       gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(),
607       bubble_view->GetMaximumSize().width() - (used_width + insets.width()));
608   // Do a binary search for the minimum width that ensures no more than three
609   // lines are needed. The lower bound is the minimum of the current bubble
610   // width and the width of the link (as no wrapping is permitted inside the
611   // link). The upper bound is the maximum of the largest allowed bubble width
612   // and the sum of the label text and link widths when put on a single line.
613   std::vector<base::string16> lines;
614   while (min_width < max_width) {
615     lines.clear();
616     const int width = (min_width + max_width) / 2;
617     const bool too_narrow =
618         gfx::ElideRectangleText(text_, font_list, width, INT_MAX,
619                                 gfx::TRUNCATE_LONG_WORDS, &lines) != 0;
620     int line_count = lines.size();
621     if (!too_narrow && line_count == 3 &&
622         width - gfx::GetStringWidth(lines.back(), font_list) <=
623             space_width + link_size.width())
624       ++line_count;
625     if (too_narrow || line_count > 3)
626       min_width = width + 1;
627     else
628       max_width = width;
629   }
630
631   // Calculate the corresponding height and set the preferred size.
632   lines.clear();
633   gfx::ElideRectangleText(
634       text_, font_list, min_width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines);
635   int line_count = lines.size();
636   if (min_width - gfx::GetStringWidth(lines.back(), font_list) <=
637           space_width + link_size.width()) {
638     ++line_count;
639   }
640   const int line_height = font_list.GetHeight();
641   const int link_extra_height = std::max(
642       link_size.height() - learn_more_->GetInsets().top() - line_height, 0);
643   preferred_size_ = gfx::Size(
644       min_width + insets.width(),
645       line_count * line_height + link_extra_height + insets.height());
646
647   bubble_view->SetWidth(preferred_size_.width() + used_width);
648 }
649
650 UserCard::UserCard(views::ButtonListener* listener, bool active_user)
651     : CustomButton(listener),
652       is_active_user_(active_user),
653       button_hovered_(false),
654       show_border_(false) {
655   if (is_active_user_) {
656     set_background(
657         views::Background::CreateSolidBackground(kBackgroundColor));
658     ShowActive();
659   }
660 }
661
662 UserCard::~UserCard() {}
663
664 void UserCard::ForceBorderVisible(bool show) {
665   show_border_ = show;
666   ShowActive();
667 }
668
669 void UserCard::OnMouseEntered(const ui::MouseEvent& event) {
670   if (is_active_user_) {
671     button_hovered_ = true;
672     background()->SetNativeControlColor(kHoverBackgroundColor);
673     ShowActive();
674   }
675 }
676
677 void UserCard::OnMouseExited(const ui::MouseEvent& event) {
678   if (is_active_user_) {
679     button_hovered_ = false;
680     background()->SetNativeControlColor(kBackgroundColor);
681     ShowActive();
682   }
683 }
684
685 void UserCard::ShowActive() {
686   int width = button_hovered_ || show_border_ ? 1 : 0;
687   SetBorder(views::Border::CreateSolidSidedBorder(
688       width, width, width, 1, kBorderColor));
689   SchedulePaint();
690 }
691
692 UserView::UserView(SystemTrayItem* owner,
693                    user::LoginStatus login,
694                    MultiProfileIndex index)
695     : multiprofile_index_(index),
696       user_card_view_(NULL),
697       owner_(owner),
698       is_user_card_(false),
699       logout_button_(NULL),
700       add_user_visible_but_disabled_(false) {
701   CHECK_NE(user::LOGGED_IN_NONE, login);
702   if (!index) {
703     // Only the logged in user will have a background. All other users will have
704     // to allow the TrayPopupContainer highlighting the menu line.
705     set_background(views::Background::CreateSolidBackground(
706         login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor :
707                                           kBackgroundColor));
708   }
709   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
710                                         kTrayPopupPaddingBetweenItems));
711   // The logout button must be added before the user card so that the user card
712   // can correctly calculate the remaining available width.
713   // Note that only the current multiprofile user gets a button.
714   if (!multiprofile_index_)
715     AddLogoutButton(login);
716   AddUserCard(owner, login);
717 }
718
719 UserView::~UserView() {}
720
721 void UserView::MouseMovedOutOfHost() {
722   popup_message_.reset();
723   mouse_watcher_.reset();
724   add_menu_option_.reset();
725 }
726
727 TrayUser::TestState UserView::GetStateForTest() const {
728   if (add_menu_option_.get()) {
729     return add_user_visible_but_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED :
730                                             TrayUser::ACTIVE;
731   }
732
733   if (!is_user_card_)
734     return TrayUser::SHOWN;
735
736   return static_cast<UserCard*>(user_card_view_)->is_hovered_for_test() ?
737       TrayUser::HOVERED : TrayUser::SHOWN;
738 }
739
740 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
741   DCHECK(user_card_view_);
742   return user_card_view_->GetBoundsInScreen();
743 }
744
745 gfx::Size UserView::GetPreferredSize() {
746   gfx::Size size = views::View::GetPreferredSize();
747   // Only the active user panel will be forced to a certain height.
748   if (!multiprofile_index_) {
749     size.set_height(std::max(size.height(),
750                              kTrayPopupItemHeight + GetInsets().height()));
751   }
752   return size;
753 }
754
755 int UserView::GetHeightForWidth(int width) {
756   return GetPreferredSize().height();
757 }
758
759 void UserView::Layout() {
760   gfx::Rect contents_area(GetContentsBounds());
761   if (user_card_view_ && logout_button_) {
762     // Give the logout button the space it requests.
763     gfx::Rect logout_area = contents_area;
764     logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
765     logout_area.set_x(contents_area.right() - logout_area.width());
766
767     // Give the remaining space to the user card.
768     gfx::Rect user_card_area = contents_area;
769     int remaining_width = contents_area.width() - logout_area.width();
770     if (SupportsMultiProfile()) {
771       // In multiprofile case |user_card_view_| and |logout_button_| have to
772       // have the same height.
773       int y = std::min(user_card_area.y(), logout_area.y());
774       int height = std::max(user_card_area.height(), logout_area.height());
775       logout_area.set_y(y);
776       logout_area.set_height(height);
777       user_card_area.set_y(y);
778       user_card_area.set_height(height);
779
780       // In multiprofile mode we have also to increase the size of the card by
781       // the size of the border to make it overlap with the logout button.
782       user_card_area.set_width(std::max(0, remaining_width + 1));
783
784       // To make the logout button symmetrical with the user card we also make
785       // the button longer by the same size the hover area in front of the icon
786       // got inset.
787       logout_area.set_width(logout_area.width() +
788                             kTrayUserTileHoverBorderInset);
789     } else {
790       // In all other modes we have to make sure that there is enough spacing
791       // between the two.
792       remaining_width -= kTrayPopupPaddingBetweenItems;
793     }
794     user_card_area.set_width(remaining_width);
795     user_card_view_->SetBoundsRect(user_card_area);
796     logout_button_->SetBoundsRect(logout_area);
797   } else if (user_card_view_) {
798     user_card_view_->SetBoundsRect(contents_area);
799   } else if (logout_button_) {
800     logout_button_->SetBoundsRect(contents_area);
801   }
802 }
803
804 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
805   if (sender == logout_button_) {
806     Shell::GetInstance()->metrics()->RecordUserMetricsAction(
807         ash::UMA_STATUS_AREA_SIGN_OUT);
808     Shell::GetInstance()->system_tray_delegate()->SignOut();
809   } else if (sender == user_card_view_ && SupportsMultiProfile()) {
810     if (!multiprofile_index_) {
811       ToggleAddUserMenuOption();
812     } else {
813       SwitchUser(multiprofile_index_);
814       // Since the user list is about to change the system menu should get
815       // closed.
816       owner_->system_tray()->CloseSystemBubble();
817     }
818   } else if (add_menu_option_.get() &&
819              sender == add_menu_option_->GetContentsView()) {
820     // Let the user add another account to the session.
821     MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
822     Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
823     owner_->system_tray()->CloseSystemBubble();
824   } else {
825     NOTREACHED();
826   }
827 }
828
829 void UserView::AddLogoutButton(user::LoginStatus login) {
830   const base::string16 title = user::GetLocalizedSignOutStringForStatus(login,
831                                                                         true);
832   TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title);
833   logout_button->SetAccessibleName(title);
834   logout_button_ = logout_button;
835   // In public account mode, the logout button border has a custom color.
836   if (login == user::LOGGED_IN_PUBLIC) {
837     scoped_ptr<TrayPopupLabelButtonBorder> border(
838         new TrayPopupLabelButtonBorder());
839     border->SetPainter(false, views::Button::STATE_NORMAL,
840                        views::Painter::CreateImageGridPainter(
841                            kPublicAccountLogoutButtonBorderImagesNormal));
842     border->SetPainter(false, views::Button::STATE_HOVERED,
843                        views::Painter::CreateImageGridPainter(
844                            kPublicAccountLogoutButtonBorderImagesHovered));
845     border->SetPainter(false, views::Button::STATE_PRESSED,
846                        views::Painter::CreateImageGridPainter(
847                            kPublicAccountLogoutButtonBorderImagesHovered));
848     logout_button_->SetBorder(border.PassAs<views::Border>());
849   }
850   AddChildView(logout_button_);
851 }
852
853 void UserView::AddUserCard(SystemTrayItem* owner, user::LoginStatus login) {
854   // Add padding around the panel.
855   SetBorder(views::Border::CreateEmptyBorder(kUserCardVerticalPadding,
856                                              kTrayPopupPaddingHorizontal,
857                                              kUserCardVerticalPadding,
858                                              kTrayPopupPaddingHorizontal));
859
860   if (SupportsMultiProfile() && login != user::LOGGED_IN_RETAIL_MODE) {
861     user_card_view_ = new UserCard(this, multiprofile_index_ == 0);
862     is_user_card_ = true;
863   } else {
864     user_card_view_ = new views::View();
865     is_user_card_ = false;
866   }
867
868   user_card_view_->SetLayoutManager(new views::BoxLayout(
869       views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
870   AddChildViewAt(user_card_view_, 0);
871
872   if (login == user::LOGGED_IN_RETAIL_MODE) {
873     AddLoggedInRetailModeUserCardContent();
874     return;
875   }
876
877   // The entire user card should trigger hover (the inner items get disabled).
878   user_card_view_->SetEnabled(true);
879   user_card_view_->set_notify_enter_exit_on_child(true);
880
881   if (login == user::LOGGED_IN_PUBLIC) {
882     AddLoggedInPublicModeUserCardContent(owner);
883     return;
884   }
885
886   views::View* icon = CreateIconForUserCard(login);
887   user_card_view_->AddChildView(icon);
888
889   // To allow the border to start before the icon, reduce the size before and
890   // add an inset to the icon to get the spacing.
891   if (multiprofile_index_ == 0 && SupportsMultiProfile()) {
892     icon->SetBorder(views::Border::CreateEmptyBorder(
893         0, kTrayUserTileHoverBorderInset, 0, 0));
894     SetBorder(views::Border::CreateEmptyBorder(
895         kUserCardVerticalPadding,
896         kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
897         kUserCardVerticalPadding,
898         kTrayPopupPaddingHorizontal));
899   }
900   SessionStateDelegate* delegate =
901       Shell::GetInstance()->session_state_delegate();
902   views::Label* username = NULL;
903   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
904   if (!multiprofile_index_) {
905     base::string16 user_name_string =
906         login == user::LOGGED_IN_GUEST ?
907             bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL) :
908             delegate->GetUserDisplayName(multiprofile_index_);
909     if (!user_name_string.empty()) {
910       username = new views::Label(user_name_string);
911       username->SetHorizontalAlignment(gfx::ALIGN_LEFT);
912     }
913   }
914
915   views::Label* additional = NULL;
916   if (login != user::LOGGED_IN_GUEST) {
917     base::string16 user_email_string =
918         login == user::LOGGED_IN_LOCALLY_MANAGED ?
919             bundle.GetLocalizedString(
920                 IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) :
921             base::UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index_));
922     if (!user_email_string.empty()) {
923       additional = new views::Label(user_email_string);
924       additional->SetFontList(
925           bundle.GetFontList(ui::ResourceBundle::SmallFont));
926       additional->SetHorizontalAlignment(gfx::ALIGN_LEFT);
927     }
928   }
929
930   // Adjust text properties dependent on if it is an active or inactive user.
931   if (multiprofile_index_) {
932     // Fade the text of non active users to 50%.
933     SkColor text_color = additional->enabled_color();
934     text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2);
935     if (additional)
936       additional->SetDisabledColor(text_color);
937     if (username)
938       username->SetDisabledColor(text_color);
939   }
940
941   if (additional && username) {
942     views::View* details = new views::View;
943     details->SetLayoutManager(new views::BoxLayout(
944         views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0));
945     details->AddChildView(username);
946     details->AddChildView(additional);
947     user_card_view_->AddChildView(details);
948   } else {
949     if (username)
950       user_card_view_->AddChildView(username);
951     if (additional)
952       user_card_view_->AddChildView(additional);
953   }
954 }
955
956 views::View* UserView::CreateIconForUserCard(user::LoginStatus login) {
957   RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
958                                                 multiprofile_index_ == 0);
959   icon->SetEnabled(false);
960   if (login == user::LOGGED_IN_GUEST) {
961     icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
962         GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON).ToImageSkia(),
963         gfx::Size(kUserIconSize, kUserIconSize));
964   } else {
965     icon->SetImage(
966         Shell::GetInstance()->session_state_delegate()->
967             GetUserImage(multiprofile_index_),
968         gfx::Size(kUserIconSize, kUserIconSize));
969   }
970   return icon;
971 }
972
973 void UserView::AddLoggedInRetailModeUserCardContent() {
974   views::Label* details = new views::Label;
975   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
976   details->SetText(
977       bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL));
978   details->SetBorder(views::Border::CreateEmptyBorder(0, 4, 0, 1));
979   details->SetHorizontalAlignment(gfx::ALIGN_LEFT);
980   user_card_view_->AddChildView(details);
981 }
982
983 void UserView::AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner) {
984   user_card_view_->AddChildView(CreateIconForUserCard(user::LOGGED_IN_PUBLIC));
985   user_card_view_->AddChildView(new PublicAccountUserDetails(
986       owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems));
987 }
988
989 void UserView::ToggleAddUserMenuOption() {
990   if (add_menu_option_.get()) {
991     popup_message_.reset();
992     mouse_watcher_.reset();
993     add_menu_option_.reset();
994     return;
995   }
996
997   // Note: We do not need to install a global event handler to delete this
998   // item since it will destroyed automatically before the menu / user menu item
999   // gets destroyed..
1000   const SessionStateDelegate* session_state_delegate =
1001       Shell::GetInstance()->session_state_delegate();
1002   add_user_visible_but_disabled_ =
1003       session_state_delegate->NumberOfLoggedInUsers() >=
1004           session_state_delegate->GetMaximumNumberOfLoggedInUsers();
1005   add_menu_option_.reset(new views::Widget);
1006   views::Widget::InitParams params;
1007   params.type = views::Widget::InitParams::TYPE_TOOLTIP;
1008   params.keep_on_top = true;
1009   params.context = this->GetWidget()->GetNativeWindow();
1010   params.accept_events = true;
1011   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
1012   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
1013   add_menu_option_->Init(params);
1014   add_menu_option_->SetOpacity(0xFF);
1015   add_menu_option_->GetNativeWindow()->set_owned_by_parent(false);
1016   SetShadowType(add_menu_option_->GetNativeView(),
1017                 views::corewm::SHADOW_TYPE_NONE);
1018
1019   // Position it below our user card.
1020   gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
1021   bounds.set_y(bounds.y() + bounds.height());
1022   add_menu_option_->SetBounds(bounds);
1023
1024   // Show the content.
1025   AddUserView* add_user_view = new AddUserView(
1026       static_cast<UserCard*>(user_card_view_), this);
1027   add_menu_option_->SetContentsView(add_user_view);
1028   add_menu_option_->SetAlwaysOnTop(true);
1029   add_menu_option_->Show();
1030   if (add_user_visible_but_disabled_) {
1031     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
1032     popup_message_.reset(new PopupMessage(
1033         bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
1034         bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER),
1035         PopupMessage::ICON_WARNING,
1036         add_user_view->anchor(),
1037         views::BubbleBorder::TOP_LEFT,
1038         gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
1039         2 * kPopupMessageOffset));
1040   }
1041   // Find the screen area which encloses both elements and sets then a mouse
1042   // watcher which will close the "menu".
1043   gfx::Rect area = user_card_view_->GetBoundsInScreen();
1044   area.set_height(2 * area.height());
1045   mouse_watcher_.reset(new views::MouseWatcher(
1046       new UserViewMouseWatcherHost(area),
1047       this));
1048   mouse_watcher_->Start();
1049 }
1050
1051 bool UserView::SupportsMultiProfile() {
1052   // We do not want to see any multi profile additions to a user view when the
1053   // log in screen is shown.
1054   return Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() &&
1055       !Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked();
1056 }
1057
1058 AddUserView::AddUserView(UserCard* owner, views::ButtonListener* listener)
1059     : CustomButton(listener),
1060       add_user_(NULL),
1061       listener_(listener),
1062       owner_(owner),
1063       anchor_(NULL) {
1064   AddContent();
1065   owner_->ForceBorderVisible(true);
1066 }
1067
1068 AddUserView::~AddUserView() {
1069   owner_->ForceBorderVisible(false);
1070 }
1071
1072 gfx::Size AddUserView::GetPreferredSize() {
1073   return owner_->bounds().size();
1074 }
1075
1076 int AddUserView::GetHeightForWidth(int width) {
1077   return owner_->bounds().size().height();
1078 }
1079
1080 void AddUserView::Layout() {
1081   gfx::Rect contents_area(GetContentsBounds());
1082   add_user_->SetBoundsRect(contents_area);
1083 }
1084
1085 void AddUserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
1086   if (add_user_ == sender)
1087     listener_->ButtonPressed(this, event);
1088   else
1089     NOTREACHED();
1090 }
1091
1092 void AddUserView::AddContent() {
1093   set_notify_enter_exit_on_child(true);
1094
1095   const SessionStateDelegate* delegate =
1096       Shell::GetInstance()->session_state_delegate();
1097   bool enable = delegate->NumberOfLoggedInUsers() <
1098                     delegate->GetMaximumNumberOfLoggedInUsers();
1099
1100   SetLayoutManager(new views::FillLayout());
1101   set_background(views::Background::CreateSolidBackground(kBackgroundColor));
1102
1103   // Add padding around the panel.
1104   SetBorder(views::Border::CreateSolidBorder(1, kBorderColor));
1105
1106   add_user_ = new UserCard(this, enable);
1107   add_user_->SetBorder(views::Border::CreateEmptyBorder(
1108       kUserCardVerticalPadding,
1109       kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
1110       kUserCardVerticalPadding,
1111       kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset));
1112
1113   add_user_->SetLayoutManager(new views::BoxLayout(
1114       views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
1115   AddChildViewAt(add_user_, 0);
1116
1117   // Add the [+] icon which is also the anchor for messages.
1118   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
1119   RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
1120                                                 true);
1121   anchor_ = icon;
1122   icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
1123       GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER).ToImageSkia(),
1124       gfx::Size(kUserIconSize, kUserIconSize));
1125   add_user_->AddChildView(icon);
1126
1127   // Add the command text.
1128   views::Label* command_label = new views::Label(
1129       bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
1130   command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1131   add_user_->AddChildView(command_label);
1132 }
1133
1134 }  // namespace tray
1135
1136 TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index)
1137     : SystemTrayItem(system_tray),
1138       multiprofile_index_(index),
1139       user_(NULL),
1140       layout_view_(NULL),
1141       avatar_(NULL),
1142       label_(NULL) {
1143   Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this);
1144 }
1145
1146 TrayUser::~TrayUser() {
1147   Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this);
1148 }
1149
1150 TrayUser::TestState TrayUser::GetStateForTest() const {
1151   if (!user_)
1152     return HIDDEN;
1153   return user_->GetStateForTest();
1154 }
1155
1156 bool TrayUser::CanDropWindowHereToTransferToUser(
1157     const gfx::Point& point_in_screen) {
1158   // Check that this item is shown in the system tray (which means it must have
1159   // a view there) and that the user it represents is not the current user (in
1160   // which case |GetTrayIndex()| would return NULL).
1161   if (!layout_view_ || !GetTrayIndex())
1162     return false;
1163   return layout_view_->GetBoundsInScreen().Contains(point_in_screen);
1164 }
1165
1166 bool TrayUser::TransferWindowToUser(aura::Window* window) {
1167   SessionStateDelegate* session_state_delegate =
1168       ash::Shell::GetInstance()->session_state_delegate();
1169   return session_state_delegate->TransferWindowToDesktopOfUser(window,
1170                                                                GetTrayIndex());
1171 }
1172
1173 gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const {
1174   DCHECK(user_);
1175   return user_->GetBoundsInScreenOfUserButtonForTest();
1176 }
1177
1178 views::View* TrayUser::CreateTrayView(user::LoginStatus status) {
1179   CHECK(layout_view_ == NULL);
1180
1181   layout_view_ = new views::View();
1182   layout_view_->SetLayoutManager(
1183       new views::BoxLayout(views::BoxLayout::kHorizontal,
1184                            0, 0, kUserLabelToIconPadding));
1185   UpdateAfterLoginStatusChange(status);
1186   return layout_view_;
1187 }
1188
1189 views::View* TrayUser::CreateDefaultView(user::LoginStatus status) {
1190   if (status == user::LOGGED_IN_NONE)
1191     return NULL;
1192   const SessionStateDelegate* session_state_delegate =
1193       Shell::GetInstance()->session_state_delegate();
1194
1195   // If the screen is locked show only the currently active user.
1196   if (multiprofile_index_ && session_state_delegate->IsUserSessionBlocked())
1197     return NULL;
1198
1199   CHECK(user_ == NULL);
1200
1201   int logged_in_users = session_state_delegate->NumberOfLoggedInUsers();
1202
1203   // Do not show more UserView's then there are logged in users.
1204   if (multiprofile_index_ >= logged_in_users)
1205     return NULL;
1206
1207   user_ = new tray::UserView(this, status, multiprofile_index_);
1208   return user_;
1209 }
1210
1211 views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
1212   return NULL;
1213 }
1214
1215 void TrayUser::DestroyTrayView() {
1216   layout_view_ = NULL;
1217   avatar_ = NULL;
1218   label_ = NULL;
1219 }
1220
1221 void TrayUser::DestroyDefaultView() {
1222   user_ = NULL;
1223 }
1224
1225 void TrayUser::DestroyDetailedView() {
1226 }
1227
1228 void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) {
1229   // Only the active user is represented in the tray.
1230   if (!layout_view_)
1231     return;
1232   if (GetTrayIndex() > 0 && !ash::switches::UseMultiUserTray())
1233     return;
1234   bool need_label = false;
1235   bool need_avatar = false;
1236   switch (status) {
1237     case user::LOGGED_IN_LOCKED:
1238     case user::LOGGED_IN_USER:
1239     case user::LOGGED_IN_OWNER:
1240     case user::LOGGED_IN_PUBLIC:
1241       need_avatar = true;
1242       break;
1243     case user::LOGGED_IN_LOCALLY_MANAGED:
1244       need_avatar = true;
1245       need_label = true;
1246       break;
1247     case user::LOGGED_IN_GUEST:
1248       need_label = true;
1249       break;
1250     case user::LOGGED_IN_RETAIL_MODE:
1251     case user::LOGGED_IN_KIOSK_APP:
1252     case user::LOGGED_IN_NONE:
1253       break;
1254   }
1255
1256   if ((need_avatar != (avatar_ != NULL)) ||
1257       (need_label != (label_ != NULL))) {
1258     layout_view_->RemoveAllChildViews(true);
1259     if (need_label) {
1260       label_ = new views::Label;
1261       SetupLabelForTray(label_);
1262       layout_view_->AddChildView(label_);
1263     } else {
1264       label_ = NULL;
1265     }
1266     if (need_avatar) {
1267       MultiProfileIndex tray_index = GetTrayIndex();
1268       if (!tray_index) {
1269         // The active user (index #0) will always be the first.
1270         avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true);
1271       } else {
1272         // All other users will be inactive users.
1273         avatar_ = new tray::UserSwitcherView(kProfileRoundedCornerRadius,
1274                                              tray_index);
1275       }
1276       layout_view_->AddChildView(avatar_);
1277     } else {
1278       avatar_ = NULL;
1279     }
1280   }
1281
1282   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
1283   if (status == user::LOGGED_IN_LOCALLY_MANAGED) {
1284     label_->SetText(
1285         bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL));
1286   } else if (status == user::LOGGED_IN_GUEST) {
1287     label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
1288   }
1289
1290   if (avatar_ && switches::UseAlternateShelfLayout()) {
1291     int corner_radius = GetTrayItemRadius();
1292     avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0);
1293     avatar_->SetBorder(views::Border::NullBorder());
1294   }
1295   UpdateAvatarImage(status);
1296
1297   // Update layout after setting label_ and avatar_ with new login status.
1298   UpdateLayoutOfItem();
1299 }
1300
1301 void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
1302   // Inactive users won't have a layout.
1303   if (!layout_view_)
1304     return;
1305   int corner_radius = GetTrayItemRadius();
1306   if (alignment == SHELF_ALIGNMENT_BOTTOM ||
1307       alignment == SHELF_ALIGNMENT_TOP) {
1308     if (avatar_) {
1309       if (switches::UseAlternateShelfLayout()) {
1310         if (multiprofile_index_) {
1311           avatar_->SetBorder(
1312               views::Border::CreateEmptyBorder(0, kTrayLabelSpacing, 0, 0));
1313         } else {
1314           avatar_->SetBorder(views::Border::NullBorder());
1315         }
1316         avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0);
1317       } else {
1318         avatar_->SetBorder(views::Border::CreateEmptyBorder(
1319             0,
1320             kTrayImageItemHorizontalPaddingBottomAlignment + 2,
1321             0,
1322             kTrayImageItemHorizontalPaddingBottomAlignment));
1323       }
1324     }
1325     if (label_) {
1326       label_->SetBorder(views::Border::CreateEmptyBorder(
1327           0,
1328           kTrayLabelItemHorizontalPaddingBottomAlignment,
1329           0,
1330           kTrayLabelItemHorizontalPaddingBottomAlignment));
1331     }
1332     layout_view_->SetLayoutManager(
1333         new views::BoxLayout(views::BoxLayout::kHorizontal,
1334                              0, 0, kUserLabelToIconPadding));
1335   } else {
1336     if (avatar_) {
1337       if (switches::UseAlternateShelfLayout()) {
1338         if (multiprofile_index_) {
1339           avatar_->SetBorder(
1340               views::Border::CreateEmptyBorder(kTrayLabelSpacing, 0, 0, 0));
1341         } else {
1342           avatar_->SetBorder(views::Border::NullBorder());
1343         }
1344         avatar_->SetCornerRadii(0, 0, corner_radius, corner_radius);
1345       } else {
1346         SetTrayImageItemBorder(avatar_, alignment);
1347       }
1348     }
1349     if (label_) {
1350       label_->SetBorder(views::Border::CreateEmptyBorder(
1351           kTrayLabelItemVerticalPaddingVerticalAlignment,
1352           kTrayLabelItemHorizontalPaddingBottomAlignment,
1353           kTrayLabelItemVerticalPaddingVerticalAlignment,
1354           kTrayLabelItemHorizontalPaddingBottomAlignment));
1355     }
1356     layout_view_->SetLayoutManager(
1357         new views::BoxLayout(views::BoxLayout::kVertical,
1358                              0, 0, kUserLabelToIconPadding));
1359   }
1360 }
1361
1362 void TrayUser::OnUserUpdate() {
1363   UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()->
1364       GetUserLoginStatus());
1365 }
1366
1367 void TrayUser::OnUserAddedToSession() {
1368   SessionStateDelegate* session_state_delegate =
1369       Shell::GetInstance()->session_state_delegate();
1370   // Only create views for user items which are logged in.
1371   if (GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
1372     return;
1373
1374   // Enforce a layout change that newly added items become visible.
1375   UpdateLayoutOfItem();
1376
1377   // Update the user item.
1378   UpdateAvatarImage(
1379       Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
1380 }
1381
1382 void TrayUser::UpdateAvatarImage(user::LoginStatus status) {
1383   SessionStateDelegate* session_state_delegate =
1384       Shell::GetInstance()->session_state_delegate();
1385   if (!avatar_ ||
1386       GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
1387     return;
1388
1389   int icon_size = switches::UseAlternateShelfLayout() ?
1390       kUserIconLargeSize : kUserIconSize;
1391
1392   avatar_->SetImage(
1393       Shell::GetInstance()->session_state_delegate()->GetUserImage(
1394           GetTrayIndex()),
1395       gfx::Size(icon_size, icon_size));
1396
1397   // Unit tests might come here with no images for some users.
1398   if (avatar_->size().IsEmpty())
1399     avatar_->SetSize(gfx::Size(icon_size, icon_size));
1400 }
1401
1402 MultiProfileIndex TrayUser::GetTrayIndex() {
1403   Shell* shell = Shell::GetInstance();
1404   // If multi profile is not enabled we can use the normal index.
1405   if (!shell->delegate()->IsMultiProfilesEnabled())
1406     return multiprofile_index_;
1407   // In case of multi profile we need to mirror the indices since the system
1408   // tray items are in the reverse order then the menu items.
1409   return shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() -
1410              1 - multiprofile_index_;
1411 }
1412
1413 int TrayUser::GetTrayItemRadius() {
1414   SessionStateDelegate* delegate =
1415       Shell::GetInstance()->session_state_delegate();
1416   bool is_last_item = GetTrayIndex() == (delegate->NumberOfLoggedInUsers() - 1);
1417   return is_last_item ? kUserIconLargeCornerRadius : 0;
1418 }
1419
1420 void TrayUser::UpdateLayoutOfItem() {
1421   internal::RootWindowController* controller =
1422       internal::GetRootWindowController(
1423           system_tray()->GetWidget()->GetNativeWindow()->GetRootWindow());
1424   if (controller && controller->shelf()) {
1425     UpdateAfterShelfAlignmentChange(
1426         controller->GetShelfLayoutManager()->GetAlignment());
1427   }
1428 }
1429
1430 }  // namespace internal
1431 }  // namespace ash