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