普通文本  |  1426行  |  50.26 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/user/tray_user.h"

#include <algorithm>
#include <climits>
#include <vector>

#include "ash/ash_switches.h"
#include "ash/metrics/user_metrics_recorder.h"
#include "ash/multi_profile_uma.h"
#include "ash/popup_message.h"
#include "ash/root_window_controller.h"
#include "ash/session_state_delegate.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/system/tray/system_tray.h"
#include "ash/system/tray/system_tray_delegate.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_item_view.h"
#include "ash/system/tray/tray_popup_label_button.h"
#include "ash/system/tray/tray_popup_label_button_border.h"
#include "ash/system/tray/tray_utils.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/scoped_vector.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "grit/ash_resources.h"
#include "grit/ash_strings.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/insets.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/size.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/text_elider.h"
#include "ui/gfx/text_utils.h"
#include "ui/views/border.h"
#include "ui/views/bubble/tray_bubble_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/link_listener.h"
#include "ui/views/corewm/shadow_types.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/mouse_watcher.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

namespace {

const int kUserDetailsVerticalPadding = 5;
const int kUserCardVerticalPadding = 10;
const int kProfileRoundedCornerRadius = 2;
const int kUserIconSize = 27;
const int kUserIconLargeSize = 32;
const int kUserIconLargeCornerRadius = 2;
const int kUserLabelToIconPadding = 5;
// When using multi login, this spacing is added between user icons.
const int kTrayLabelSpacing = 1;

// When a hover border is used, it is starting this many pixels before the icon
// position.
const int kTrayUserTileHoverBorderInset = 10;

// The border color of the user button.
const SkColor kBorderColor = 0xffdcdcdc;

// The invisible word joiner character, used as a marker to indicate the start
// and end of the user's display name in the public account user card's text.
const char16 kDisplayNameMark[] = { 0x2060, 0 };

const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
};

const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
    IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
};

// Offsetting the popup message relative to the tray menu.
const int kPopupMessageOffset = 25;

// Switch to a user with the given |user_index|.
void SwitchUser(ash::MultiProfileIndex user_index) {
  // Do not switch users when the log screen is presented.
  if (ash::Shell::GetInstance()->session_state_delegate()->
          IsUserSessionBlocked())
    return;

  DCHECK(user_index > 0);
  ash::SessionStateDelegate* delegate =
      ash::Shell::GetInstance()->session_state_delegate();
  ash::MultiProfileUMA::RecordSwitchActiveUser(
      ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
  delegate->SwitchActiveUser(delegate->GetUserID(user_index));
}

}  // namespace

namespace ash {
namespace internal {

namespace tray {

// A custom image view with rounded edges.
class RoundedImageView : public views::View {
 public:
  // Constructs a new rounded image view with rounded corners of radius
  // |corner_radius|. If |active_user| is set, the icon will be drawn in
  // full colors - otherwise it will fade into the background.
  RoundedImageView(int corner_radius, bool active_user);
  virtual ~RoundedImageView();

  // Set the image that should be displayed. The image contents is copied to the
  // receiver's image.
  void SetImage(const gfx::ImageSkia& img, const gfx::Size& size);

  // Set the radii of the corners independently.
  void SetCornerRadii(int top_left,
                      int top_right,
                      int bottom_right,
                      int bottom_left);

 private:
  // Overridden from views::View.
  virtual gfx::Size GetPreferredSize() OVERRIDE;
  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;

  gfx::ImageSkia image_;
  gfx::ImageSkia resized_;
  gfx::Size image_size_;
  int corner_radius_[4];

  // True if the given user is the active user and the icon should get
  // painted as active.
  bool active_user_;

  DISALLOW_COPY_AND_ASSIGN(RoundedImageView);
};

// An inactive user view which can be clicked to make active. Note that this
// "button" does not show as a button any click or hover changes.
class UserSwitcherView : public RoundedImageView {
 public:
  UserSwitcherView(int corner_radius, MultiProfileIndex user_index);
  virtual ~UserSwitcherView() {}

  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
  virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;

 private:
  // The user index to activate when the item was clicked. Note that this
  // index refers to the LRU list of logged in users.
  MultiProfileIndex user_index_;

  DISALLOW_COPY_AND_ASSIGN(UserSwitcherView);
};

// The user details shown in public account mode. This is essentially a label
// but with custom painting code as the text is styled with multiple colors and
// contains a link.
class PublicAccountUserDetails : public views::View,
                                 public views::LinkListener {
 public:
  PublicAccountUserDetails(SystemTrayItem* owner, int used_width);
  virtual ~PublicAccountUserDetails();

 private:
  // Overridden from views::View.
  virtual void Layout() OVERRIDE;
  virtual gfx::Size GetPreferredSize() OVERRIDE;
  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;

  // Overridden from views::LinkListener.
  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;

  // Calculate a preferred size that ensures the label text and the following
  // link do not wrap over more than three lines in total for aesthetic reasons
  // if possible.
  void CalculatePreferredSize(SystemTrayItem* owner, int used_width);

  base::string16 text_;
  views::Link* learn_more_;
  gfx::Size preferred_size_;
  ScopedVector<gfx::RenderText> lines_;

  DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
};

// The button which holds the user card in case of multi profile.
class UserCard : public views::CustomButton {
 public:
  UserCard(views::ButtonListener* listener, bool active_user);
  virtual ~UserCard();

  // Called when the border should remain even in the non highlighted state.
  void ForceBorderVisible(bool show);

  // Overridden from views::View
  virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
  virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;

  // Check if the item is hovered.
  bool is_hovered_for_test() {return button_hovered_; }

 private:
  // Change the hover/active state of the "button" when the status changes.
  void ShowActive();

  // True if this is the active user.
  bool is_active_user_;

  // True if button is hovered.
  bool button_hovered_;

  // True if the border should be visible.
  bool show_border_;

  DISALLOW_COPY_AND_ASSIGN(UserCard);
};

class UserViewMouseWatcherHost : public views::MouseWatcherHost {
public:
 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
     : screen_area_(screen_area) {}
 virtual ~UserViewMouseWatcherHost() {}

 // Implementation of MouseWatcherHost.
 virtual bool Contains(const gfx::Point& screen_point,
                       views::MouseWatcherHost::MouseEventType type) OVERRIDE {
   return screen_area_.Contains(screen_point);
 }

private:
 gfx::Rect screen_area_;

 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
};

// The view of a user item.
class UserView : public views::View,
                 public views::ButtonListener,
                 public views::MouseWatcherListener {
 public:
  UserView(SystemTrayItem* owner,
           ash::user::LoginStatus login,
           MultiProfileIndex index);
  virtual ~UserView();

  // Overridden from MouseWatcherListener:
  virtual void MouseMovedOutOfHost() OVERRIDE;

  TrayUser::TestState GetStateForTest() const;
  gfx::Rect GetBoundsInScreenOfUserButtonForTest();

 private:
  // Overridden from views::View.
  virtual gfx::Size GetPreferredSize() OVERRIDE;
  virtual int GetHeightForWidth(int width) OVERRIDE;
  virtual void Layout() OVERRIDE;

  // Overridden from views::ButtonListener.
  virtual void ButtonPressed(views::Button* sender,
                             const ui::Event& event) OVERRIDE;

  void AddLogoutButton(user::LoginStatus login);
  void AddUserCard(SystemTrayItem* owner, user::LoginStatus login);

  // Create a user icon representation for the user card.
  views::View* CreateIconForUserCard(user::LoginStatus login);

  // Create the additional user card content for the retail logged in mode.
  void AddLoggedInRetailModeUserCardContent();

  // Create the additional user card content for the public mode.
  void AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner);

  // Create the menu option to add another user. If |disabled| is set the user
  // cannot actively click on the item.
  void ToggleAddUserMenuOption();

  // Returns true when multi profile is supported.
  bool SupportsMultiProfile();

  MultiProfileIndex multiprofile_index_;
  // The view of the user card.
  views::View* user_card_view_;

  // This is the owner system tray item of this view.
  SystemTrayItem* owner_;

  // True if |user_card_view_| is a |UserView| - otherwise it is only a
  // |views::View|.
  bool is_user_card_;
  views::View* logout_button_;
  scoped_ptr<PopupMessage> popup_message_;
  scoped_ptr<views::Widget> add_menu_option_;

  // True when the add user panel is visible but not activatable.
  bool add_user_visible_but_disabled_;

  // The mouse watcher which takes care of out of window hover events.
  scoped_ptr<views::MouseWatcher> mouse_watcher_;

  DISALLOW_COPY_AND_ASSIGN(UserView);
};

// The menu item view which gets shown when the user clicks in multi profile
// mode onto the user item.
class AddUserView : public views::CustomButton,
                    public views::ButtonListener {
 public:
  // The |owner| is the view for which this view gets created. The |listener|
  // will get notified when this item gets clicked.
  AddUserView(UserCard* owner, views::ButtonListener* listener);
  virtual ~AddUserView();

  // Get the anchor view for a message.
  views::View* anchor() { return anchor_; }

  // Overridden from views::ButtonListener.
  virtual void ButtonPressed(views::Button* sender,
                             const ui::Event& event) OVERRIDE;

 private:
  // Overridden from views::View.
  virtual gfx::Size GetPreferredSize() OVERRIDE;
  virtual int GetHeightForWidth(int width) OVERRIDE;
  virtual void Layout() OVERRIDE;

  // Create the additional client content for this item.
  void AddContent();

  // This is the content we create and show.
  views::View* add_user_;

  // This listener will get informed when someone clicks on this button.
  views::ButtonListener* listener_;

  // This is the owner view of this item.
  UserCard* owner_;

  // The anchor view for targetted bubble messages.
  views::View* anchor_;

  DISALLOW_COPY_AND_ASSIGN(AddUserView);
};

RoundedImageView::RoundedImageView(int corner_radius, bool active_user)
    : active_user_(active_user) {
  for (int i = 0; i < 4; ++i)
    corner_radius_[i] = corner_radius;
}

RoundedImageView::~RoundedImageView() {}

void RoundedImageView::SetImage(const gfx::ImageSkia& img,
                                const gfx::Size& size) {
  image_ = img;
  image_size_ = size;

  // Try to get the best image quality for the avatar.
  resized_ = gfx::ImageSkiaOperations::CreateResizedImage(image_,
      skia::ImageOperations::RESIZE_BEST, size);
  if (GetWidget() && visible()) {
    PreferredSizeChanged();
    SchedulePaint();
  }
}

void RoundedImageView::SetCornerRadii(int top_left,
                                      int top_right,
                                      int bottom_right,
                                      int bottom_left) {
  corner_radius_[0] = top_left;
  corner_radius_[1] = top_right;
  corner_radius_[2] = bottom_right;
  corner_radius_[3] = bottom_left;
}

gfx::Size RoundedImageView::GetPreferredSize() {
  return gfx::Size(image_size_.width() + GetInsets().width(),
                   image_size_.height() + GetInsets().height());
}

void RoundedImageView::OnPaint(gfx::Canvas* canvas) {
  View::OnPaint(canvas);
  gfx::Rect image_bounds(size());
  image_bounds.ClampToCenteredSize(GetPreferredSize());
  image_bounds.Inset(GetInsets());
  const SkScalar kRadius[8] = {
    SkIntToScalar(corner_radius_[0]),
    SkIntToScalar(corner_radius_[0]),
    SkIntToScalar(corner_radius_[1]),
    SkIntToScalar(corner_radius_[1]),
    SkIntToScalar(corner_radius_[2]),
    SkIntToScalar(corner_radius_[2]),
    SkIntToScalar(corner_radius_[3]),
    SkIntToScalar(corner_radius_[3])
  };
  SkPath path;
  path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius);
  SkPaint paint;
  paint.setAntiAlias(true);
  paint.setXfermodeMode(active_user_ ? SkXfermode::kSrcOver_Mode :
                                       SkXfermode::kLuminosity_Mode);
  canvas->DrawImageInPath(resized_, image_bounds.x(), image_bounds.y(),
                          path, paint);
}

UserSwitcherView::UserSwitcherView(int corner_radius,
                                   MultiProfileIndex user_index)
    : RoundedImageView(corner_radius, false),
      user_index_(user_index) {
  SetEnabled(true);
}

void UserSwitcherView::OnMouseEvent(ui::MouseEvent* event) {
  if (event->type() == ui::ET_MOUSE_PRESSED) {
    SwitchUser(user_index_);
    event->SetHandled();
  }
}

void UserSwitcherView::OnTouchEvent(ui::TouchEvent* event) {
  if (event->type() == ui::ET_TOUCH_PRESSED) {
    SwitchUser(user_index_);
    event->SetHandled();
  }
}

PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner,
                                                   int used_width)
    : learn_more_(NULL) {
  const int inner_padding =
      kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
  const bool rtl = base::i18n::IsRTL();
  set_border(views::Border::CreateEmptyBorder(
      kUserDetailsVerticalPadding, rtl ? 0 : inner_padding,
      kUserDetailsVerticalPadding, rtl ? inner_padding : 0));

  // Retrieve the user's display name and wrap it with markers.
  // Note that since this is a public account it always has to be the primary
  // user.
  base::string16 display_name =
      Shell::GetInstance()->session_state_delegate()->GetUserDisplayName(0);
  base::RemoveChars(display_name, kDisplayNameMark, &display_name);
  display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0];
  // Retrieve the domain managing the device and wrap it with markers.
  base::string16 domain = UTF8ToUTF16(
      Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain());
  base::RemoveChars(domain, kDisplayNameMark, &domain);
  base::i18n::WrapStringWithLTRFormatting(&domain);
  // Retrieve the label text, inserting the display name and domain.
  text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL,
                                     display_name, domain);

  learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
  learn_more_->SetUnderline(false);
  learn_more_->set_listener(this);
  AddChildView(learn_more_);

  CalculatePreferredSize(owner, used_width);
}

PublicAccountUserDetails::~PublicAccountUserDetails() {}

void PublicAccountUserDetails::Layout() {
  lines_.clear();
  const gfx::Rect contents_area = GetContentsBounds();
  if (contents_area.IsEmpty())
    return;

  // Word-wrap the label text.
  const gfx::FontList font_list;
  std::vector<base::string16> lines;
  gfx::ElideRectangleText(text_, font_list, contents_area.width(),
                          contents_area.height(), gfx::ELIDE_LONG_WORDS,
                          &lines);
  // Loop through the lines, creating a renderer for each.
  gfx::Point position = contents_area.origin();
  gfx::Range display_name(gfx::Range::InvalidRange());
  for (std::vector<base::string16>::const_iterator it = lines.begin();
       it != lines.end(); ++it) {
    gfx::RenderText* line = gfx::RenderText::CreateInstance();
    line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
    line->SetText(*it);
    const gfx::Size size(contents_area.width(), line->GetStringSize().height());
    line->SetDisplayRect(gfx::Rect(position, size));
    position.set_y(position.y() + size.height());

    // Set the default text color for the line.
    line->SetColor(kPublicAccountUserCardTextColor);

    // If a range of the line contains the user's display name, apply a custom
    // text color to it.
    if (display_name.is_empty())
      display_name.set_start(it->find(kDisplayNameMark));
    if (!display_name.is_empty()) {
      display_name.set_end(
          it->find(kDisplayNameMark, display_name.start() + 1));
      gfx::Range line_range(0, it->size());
      line->ApplyColor(kPublicAccountUserCardNameColor,
                       display_name.Intersect(line_range));
      // Update the range for the next line.
      if (display_name.end() >= line_range.end())
        display_name.set_start(0);
      else
        display_name = gfx::Range::InvalidRange();
    }

    lines_.push_back(line);
  }

  // Position the link after the label text, separated by a space. If it does
  // not fit onto the last line of the text, wrap the link onto its own line.
  const gfx::Size last_line_size = lines_.back()->GetStringSize();
  const int space_width = gfx::GetStringWidth(ASCIIToUTF16(" "), font_list);
  const gfx::Size link_size = learn_more_->GetPreferredSize();
  if (contents_area.width() - last_line_size.width() >=
      space_width + link_size.width()) {
    position.set_x(position.x() + last_line_size.width() + space_width);
    position.set_y(position.y() - last_line_size.height());
  }
  position.set_y(position.y() - learn_more_->GetInsets().top());
  gfx::Rect learn_more_bounds(position, link_size);
  learn_more_bounds.Intersect(contents_area);
  if (base::i18n::IsRTL()) {
    const gfx::Insets insets = GetInsets();
    learn_more_bounds.Offset(insets.right() - insets.left(), 0);
  }
  learn_more_->SetBoundsRect(learn_more_bounds);
}

gfx::Size PublicAccountUserDetails::GetPreferredSize() {
  return preferred_size_;
}

void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) {
  for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin();
       it != lines_.end(); ++it) {
    (*it)->Draw(canvas);
  }
  views::View::OnPaint(canvas);
}

void PublicAccountUserDetails::LinkClicked(views::Link* source,
                                           int event_flags) {
  DCHECK_EQ(source, learn_more_);
  Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo();
}

void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner,
                                                      int used_width) {
  const gfx::FontList font_list;
  const gfx::Size link_size = learn_more_->GetPreferredSize();
  const int space_width = gfx::GetStringWidth(ASCIIToUTF16(" "), font_list);
  const gfx::Insets insets = GetInsets();
  views::TrayBubbleView* bubble_view =
      owner->system_tray()->GetSystemBubble()->bubble_view();
  int min_width = std::max(
      link_size.width(),
      bubble_view->GetPreferredSize().width() - (used_width + insets.width()));
  int max_width = std::min(
      gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(),
      bubble_view->GetMaximumSize().width() - (used_width + insets.width()));
  // Do a binary search for the minimum width that ensures no more than three
  // lines are needed. The lower bound is the minimum of the current bubble
  // width and the width of the link (as no wrapping is permitted inside the
  // link). The upper bound is the maximum of the largest allowed bubble width
  // and the sum of the label text and link widths when put on a single line.
  std::vector<base::string16> lines;
  while (min_width < max_width) {
    lines.clear();
    const int width = (min_width + max_width) / 2;
    const bool too_narrow =
        gfx::ElideRectangleText(text_, font_list, width, INT_MAX,
                                gfx::TRUNCATE_LONG_WORDS, &lines) != 0;
    int line_count = lines.size();
    if (!too_narrow && line_count == 3 &&
        width - gfx::GetStringWidth(lines.back(), font_list) <=
            space_width + link_size.width())
      ++line_count;
    if (too_narrow || line_count > 3)
      min_width = width + 1;
    else
      max_width = width;
  }

  // Calculate the corresponding height and set the preferred size.
  lines.clear();
  gfx::ElideRectangleText(
      text_, font_list, min_width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines);
  int line_count = lines.size();
  if (min_width - gfx::GetStringWidth(lines.back(), font_list) <=
          space_width + link_size.width()) {
    ++line_count;
  }
  const int line_height = font_list.GetHeight();
  const int link_extra_height = std::max(
      link_size.height() - learn_more_->GetInsets().top() - line_height, 0);
  preferred_size_ = gfx::Size(
      min_width + insets.width(),
      line_count * line_height + link_extra_height + insets.height());

  bubble_view->SetWidth(preferred_size_.width() + used_width);
}

UserCard::UserCard(views::ButtonListener* listener, bool active_user)
    : CustomButton(listener),
      is_active_user_(active_user),
      button_hovered_(false),
      show_border_(false) {
  if (is_active_user_) {
    set_background(
        views::Background::CreateSolidBackground(kBackgroundColor));
    ShowActive();
  }
}

UserCard::~UserCard() {}

void UserCard::ForceBorderVisible(bool show) {
  show_border_ = show;
  ShowActive();
}

void UserCard::OnMouseEntered(const ui::MouseEvent& event) {
  if (is_active_user_) {
    button_hovered_ = true;
    background()->SetNativeControlColor(kHoverBackgroundColor);
    ShowActive();
  }
}

void UserCard::OnMouseExited(const ui::MouseEvent& event) {
  if (is_active_user_) {
    button_hovered_ = false;
    background()->SetNativeControlColor(kBackgroundColor);
    ShowActive();
  }
}

void UserCard::ShowActive() {
  int width = button_hovered_ || show_border_ ? 1 : 0;
  set_border(views::Border::CreateSolidSidedBorder(width, width, width, 1,
                                                   kBorderColor));
  SchedulePaint();
}

UserView::UserView(SystemTrayItem* owner,
                   user::LoginStatus login,
                   MultiProfileIndex index)
    : multiprofile_index_(index),
      user_card_view_(NULL),
      owner_(owner),
      is_user_card_(false),
      logout_button_(NULL),
      add_user_visible_but_disabled_(false) {
  CHECK_NE(user::LOGGED_IN_NONE, login);
  if (!index) {
    // Only the logged in user will have a background. All other users will have
    // to allow the TrayPopupContainer highlighting the menu line.
    set_background(views::Background::CreateSolidBackground(
        login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor :
                                          kBackgroundColor));
  }
  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
                                        kTrayPopupPaddingBetweenItems));
  // The logout button must be added before the user card so that the user card
  // can correctly calculate the remaining available width.
  // Note that only the current multiprofile user gets a button.
  if (!multiprofile_index_)
    AddLogoutButton(login);
  AddUserCard(owner, login);
}

UserView::~UserView() {}

void UserView::MouseMovedOutOfHost() {
  popup_message_.reset();
  mouse_watcher_.reset();
  add_menu_option_.reset();
}

TrayUser::TestState UserView::GetStateForTest() const {
  if (add_menu_option_.get()) {
    return add_user_visible_but_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED :
                                            TrayUser::ACTIVE;
  }

  if (!is_user_card_)
    return TrayUser::SHOWN;

  return static_cast<UserCard*>(user_card_view_)->is_hovered_for_test() ?
      TrayUser::HOVERED : TrayUser::SHOWN;
}

gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
  DCHECK(user_card_view_);
  return user_card_view_->GetBoundsInScreen();
}

gfx::Size UserView::GetPreferredSize() {
  gfx::Size size = views::View::GetPreferredSize();
  // Only the active user panel will be forced to a certain height.
  if (!multiprofile_index_) {
    size.set_height(std::max(size.height(),
                             kTrayPopupItemHeight + GetInsets().height()));
  }
  return size;
}

int UserView::GetHeightForWidth(int width) {
  return GetPreferredSize().height();
}

void UserView::Layout() {
  gfx::Rect contents_area(GetContentsBounds());
  if (user_card_view_ && logout_button_) {
    // Give the logout button the space it requests.
    gfx::Rect logout_area = contents_area;
    logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
    logout_area.set_x(contents_area.right() - logout_area.width());

    // Give the remaining space to the user card.
    gfx::Rect user_card_area = contents_area;
    int remaining_width = contents_area.width() - logout_area.width();
    if (SupportsMultiProfile()) {
      // In multiprofile case |user_card_view_| and |logout_button_| have to
      // have the same height.
      int y = std::min(user_card_area.y(), logout_area.y());
      int height = std::max(user_card_area.height(), logout_area.height());
      logout_area.set_y(y);
      logout_area.set_height(height);
      user_card_area.set_y(y);
      user_card_area.set_height(height);

      // In multiprofile mode we have also to increase the size of the card by
      // the size of the border to make it overlap with the logout button.
      user_card_area.set_width(std::max(0, remaining_width + 1));

      // To make the logout button symmetrical with the user card we also make
      // the button longer by the same size the hover area in front of the icon
      // got inset.
      logout_area.set_width(logout_area.width() +
                            kTrayUserTileHoverBorderInset);
    } else {
      // In all other modes we have to make sure that there is enough spacing
      // between the two.
      remaining_width -= kTrayPopupPaddingBetweenItems;
    }
    user_card_area.set_width(remaining_width);
    user_card_view_->SetBoundsRect(user_card_area);
    logout_button_->SetBoundsRect(logout_area);
  } else if (user_card_view_) {
    user_card_view_->SetBoundsRect(contents_area);
  } else if (logout_button_) {
    logout_button_->SetBoundsRect(contents_area);
  }
}

void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
  if (sender == logout_button_) {
    Shell::GetInstance()->metrics()->RecordUserMetricsAction(
        ash::UMA_STATUS_AREA_SIGN_OUT);
    Shell::GetInstance()->system_tray_delegate()->SignOut();
  } else if (sender == user_card_view_ && SupportsMultiProfile()) {
    if (!multiprofile_index_) {
      ToggleAddUserMenuOption();
    } else {
      SwitchUser(multiprofile_index_);
      // Since the user list is about to change the system menu should get
      // closed.
      owner_->system_tray()->CloseSystemBubble();
    }
  } else if (add_menu_option_.get() &&
             sender == add_menu_option_->GetContentsView()) {
    // Let the user add another account to the session.
    MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
    Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
  } else {
    NOTREACHED();
  }
}

void UserView::AddLogoutButton(user::LoginStatus login) {
  const base::string16 title = user::GetLocalizedSignOutStringForStatus(login,
                                                                        true);
  TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title);
  logout_button->SetAccessibleName(title);
  logout_button_ = logout_button;
  // In public account mode, the logout button border has a custom color.
  if (login == user::LOGGED_IN_PUBLIC) {
    TrayPopupLabelButtonBorder* border =
        static_cast<TrayPopupLabelButtonBorder*>(logout_button_->border());
    border->SetPainter(false, views::Button::STATE_NORMAL,
                       views::Painter::CreateImageGridPainter(
                           kPublicAccountLogoutButtonBorderImagesNormal));
    border->SetPainter(false, views::Button::STATE_HOVERED,
                       views::Painter::CreateImageGridPainter(
                           kPublicAccountLogoutButtonBorderImagesHovered));
    border->SetPainter(false, views::Button::STATE_PRESSED,
                       views::Painter::CreateImageGridPainter(
                           kPublicAccountLogoutButtonBorderImagesHovered));
  }
  AddChildView(logout_button_);
}

void UserView::AddUserCard(SystemTrayItem* owner, user::LoginStatus login) {
  // Add padding around the panel.
  set_border(views::Border::CreateEmptyBorder(
      kUserCardVerticalPadding, kTrayPopupPaddingHorizontal,
      kUserCardVerticalPadding, kTrayPopupPaddingHorizontal));

  if (SupportsMultiProfile() && login != user::LOGGED_IN_RETAIL_MODE) {
    user_card_view_ = new UserCard(this, multiprofile_index_ == 0);
    is_user_card_ = true;
  } else {
    user_card_view_ = new views::View();
    is_user_card_ = false;
  }

  user_card_view_->SetLayoutManager(new views::BoxLayout(
      views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
  AddChildViewAt(user_card_view_, 0);

  if (login == user::LOGGED_IN_RETAIL_MODE) {
    AddLoggedInRetailModeUserCardContent();
    return;
  }

  // The entire user card should trigger hover (the inner items get disabled).
  user_card_view_->SetEnabled(true);
  user_card_view_->set_notify_enter_exit_on_child(true);

  if (login == user::LOGGED_IN_PUBLIC) {
    AddLoggedInPublicModeUserCardContent(owner);
    return;
  }

  views::View* icon = CreateIconForUserCard(login);
  user_card_view_->AddChildView(icon);

  // To allow the border to start before the icon, reduce the size before and
  // add an inset to the icon to get the spacing.
  if (multiprofile_index_ == 0 && SupportsMultiProfile()) {
    icon->set_border(views::Border::CreateEmptyBorder(
        0, kTrayUserTileHoverBorderInset, 0, 0));
    set_border(views::Border::CreateEmptyBorder(
        kUserCardVerticalPadding,
        kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
        kUserCardVerticalPadding,
        kTrayPopupPaddingHorizontal));
  }
  SessionStateDelegate* delegate =
      Shell::GetInstance()->session_state_delegate();
  views::Label* username = NULL;
  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
  if (!multiprofile_index_) {
    base::string16 user_name_string =
        login == user::LOGGED_IN_GUEST ?
            bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL) :
            delegate->GetUserDisplayName(multiprofile_index_);
    if (!user_name_string.empty()) {
      username = new views::Label(user_name_string);
      username->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    }
  }

  views::Label* additional = NULL;
  if (login != user::LOGGED_IN_GUEST) {
    base::string16 user_email_string =
        login == user::LOGGED_IN_LOCALLY_MANAGED ?
            bundle.GetLocalizedString(
                IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) :
            UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index_));
    if (!user_email_string.empty()) {
      additional = new views::Label(user_email_string);
      additional->SetFontList(
          bundle.GetFontList(ui::ResourceBundle::SmallFont));
      additional->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    }
  }

  // Adjust text properties dependent on if it is an active or inactive user.
  if (multiprofile_index_) {
    // Fade the text of non active users to 50%.
    SkColor text_color = additional->enabled_color();
    text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2);
    if (additional)
      additional->SetDisabledColor(text_color);
    if (username)
      username->SetDisabledColor(text_color);
  }

  if (additional && username) {
    views::View* details = new views::View;
    details->SetLayoutManager(new views::BoxLayout(
        views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0));
    details->AddChildView(username);
    details->AddChildView(additional);
    user_card_view_->AddChildView(details);
  } else {
    if (username)
      user_card_view_->AddChildView(username);
    if (additional)
      user_card_view_->AddChildView(additional);
  }
}

views::View* UserView::CreateIconForUserCard(user::LoginStatus login) {
  RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
                                                multiprofile_index_ == 0);
  icon->SetEnabled(false);
  if (login == user::LOGGED_IN_GUEST) {
    icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
        GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON).ToImageSkia(),
        gfx::Size(kUserIconSize, kUserIconSize));
  } else {
    icon->SetImage(
        Shell::GetInstance()->session_state_delegate()->
            GetUserImage(multiprofile_index_),
        gfx::Size(kUserIconSize, kUserIconSize));
  }
  return icon;
}

void UserView::AddLoggedInRetailModeUserCardContent() {
  views::Label* details = new views::Label;
  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
  details->SetText(
      bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL));
  details->set_border(views::Border::CreateEmptyBorder(0, 4, 0, 1));
  details->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  user_card_view_->AddChildView(details);
}

void UserView::AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner) {
  user_card_view_->AddChildView(CreateIconForUserCard(user::LOGGED_IN_PUBLIC));
  user_card_view_->AddChildView(new PublicAccountUserDetails(
      owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems));
}

void UserView::ToggleAddUserMenuOption() {
  if (add_menu_option_.get()) {
    popup_message_.reset();
    mouse_watcher_.reset();
    add_menu_option_.reset();
    return;
  }

  // Note: We do not need to install a global event handler to delete this
  // item since it will destroyed automatically before the menu / user menu item
  // gets destroyed..
  const SessionStateDelegate* session_state_delegate =
      Shell::GetInstance()->session_state_delegate();
  add_user_visible_but_disabled_ =
      session_state_delegate->NumberOfLoggedInUsers() >=
          session_state_delegate->GetMaximumNumberOfLoggedInUsers();
  add_menu_option_.reset(new views::Widget);
  views::Widget::InitParams params;
  params.type = views::Widget::InitParams::TYPE_TOOLTIP;
  params.keep_on_top = true;
  params.context = this->GetWidget()->GetNativeWindow();
  params.accept_events = true;
  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
  add_menu_option_->Init(params);
  add_menu_option_->SetOpacity(0xFF);
  add_menu_option_->GetNativeWindow()->set_owned_by_parent(false);
  SetShadowType(add_menu_option_->GetNativeView(),
                views::corewm::SHADOW_TYPE_NONE);

  // Position it below our user card.
  gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
  bounds.set_y(bounds.y() + bounds.height());
  add_menu_option_->SetBounds(bounds);

  // Show the content.
  AddUserView* add_user_view = new AddUserView(
      static_cast<UserCard*>(user_card_view_), this);
  add_menu_option_->SetContentsView(add_user_view);
  add_menu_option_->SetAlwaysOnTop(true);
  add_menu_option_->Show();
  if (add_user_visible_but_disabled_) {
    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    popup_message_.reset(new PopupMessage(
        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER),
        PopupMessage::ICON_WARNING,
        add_user_view->anchor(),
        views::BubbleBorder::TOP_LEFT,
        gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
        2 * kPopupMessageOffset));
  }
  // Find the screen area which encloses both elements and sets then a mouse
  // watcher which will close the "menu".
  gfx::Rect area = user_card_view_->GetBoundsInScreen();
  area.set_height(2 * area.height());
  mouse_watcher_.reset(new views::MouseWatcher(
      new UserViewMouseWatcherHost(area),
      this));
  mouse_watcher_->Start();
}

bool UserView::SupportsMultiProfile() {
  // We do not want to see any multi profile additions to a user view when the
  // log in screen is shown.
  return Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() &&
      !Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked();
}

AddUserView::AddUserView(UserCard* owner, views::ButtonListener* listener)
    : CustomButton(listener_),
      add_user_(NULL),
      listener_(listener),
      owner_(owner),
      anchor_(NULL) {
  AddContent();
  owner_->ForceBorderVisible(true);
}

AddUserView::~AddUserView() {
  owner_->ForceBorderVisible(false);
}

gfx::Size AddUserView::GetPreferredSize() {
  return owner_->bounds().size();
}

int AddUserView::GetHeightForWidth(int width) {
  return owner_->bounds().size().height();
}

void AddUserView::Layout() {
  gfx::Rect contents_area(GetContentsBounds());
  add_user_->SetBoundsRect(contents_area);
}

void AddUserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
  if (add_user_ == sender)
    listener_->ButtonPressed(this, event);
  else
    NOTREACHED();
}

void AddUserView::AddContent() {
  set_notify_enter_exit_on_child(true);

  const SessionStateDelegate* delegate =
      Shell::GetInstance()->session_state_delegate();
  bool enable = delegate->NumberOfLoggedInUsers() <
                    delegate->GetMaximumNumberOfLoggedInUsers();

  SetLayoutManager(new views::FillLayout());
  set_background(views::Background::CreateSolidBackground(kBackgroundColor));

  // Add padding around the panel.
  set_border(views::Border::CreateSolidBorder(1, kBorderColor));

  add_user_ = new UserCard(this, enable);
  add_user_->set_border(views::Border::CreateEmptyBorder(
      kUserCardVerticalPadding,
      kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset,
      kUserCardVerticalPadding,
      kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset));

  add_user_->SetLayoutManager(new views::BoxLayout(
      views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
  AddChildViewAt(add_user_, 0);

  // Add the [+] icon which is also the anchor for messages.
  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
  RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
                                                true);
  anchor_ = icon;
  icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
      GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER).ToImageSkia(),
      gfx::Size(kUserIconSize, kUserIconSize));
  add_user_->AddChildView(icon);

  // Add the command text.
  views::Label* command_label = new views::Label(
      bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
  command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  add_user_->AddChildView(command_label);
}

}  // namespace tray

TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index)
    : SystemTrayItem(system_tray),
      multiprofile_index_(index),
      user_(NULL),
      layout_view_(NULL),
      avatar_(NULL),
      label_(NULL) {
  Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this);
}

TrayUser::~TrayUser() {
  Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this);
}

TrayUser::TestState TrayUser::GetStateForTest() const {
  if (!user_)
    return HIDDEN;
  return user_->GetStateForTest();
}

bool TrayUser::CanDropWindowHereToTransferToUser(
    const gfx::Point& point_in_screen) {
  // Check that this item is shown in the system tray (which means it must have
  // a view there) and that the user it represents is not the current user (in
  // which case |GetTrayIndex()| would return NULL).
  if (!layout_view_ || !GetTrayIndex())
    return false;
  return layout_view_->GetBoundsInScreen().Contains(point_in_screen);
}

bool TrayUser::TransferWindowToUser(aura::Window* window) {
  SessionStateDelegate* session_state_delegate =
      ash::Shell::GetInstance()->session_state_delegate();
  return session_state_delegate->TransferWindowToDesktopOfUser(window,
                                                               GetTrayIndex());
}

gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const {
  DCHECK(user_);
  return user_->GetBoundsInScreenOfUserButtonForTest();
}

views::View* TrayUser::CreateTrayView(user::LoginStatus status) {
  CHECK(layout_view_ == NULL);
  // When the full multi profile mode is used, only the active user will be
  // shown in the system tray, otherwise all users which are logged in.
  if (GetTrayIndex() && switches::UseFullMultiProfileMode())
    return NULL;

  layout_view_ = new views::View();
  layout_view_->SetLayoutManager(
      new views::BoxLayout(views::BoxLayout::kHorizontal,
                           0, 0, kUserLabelToIconPadding));
  UpdateAfterLoginStatusChange(status);
  return layout_view_;
}

views::View* TrayUser::CreateDefaultView(user::LoginStatus status) {
  if (status == user::LOGGED_IN_NONE)
    return NULL;
  const SessionStateDelegate* session_state_delegate =
      Shell::GetInstance()->session_state_delegate();

  // If the screen is locked show only the currently active user.
  if (multiprofile_index_ && session_state_delegate->IsUserSessionBlocked())
    return NULL;

  CHECK(user_ == NULL);

  int logged_in_users = session_state_delegate->NumberOfLoggedInUsers();

  // Do not show more UserView's then there are logged in users.
  if (multiprofile_index_ >= logged_in_users)
    return NULL;

  user_ = new tray::UserView(this, status, multiprofile_index_);
  return user_;
}

views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
  return NULL;
}

void TrayUser::DestroyTrayView() {
  layout_view_ = NULL;
  avatar_ = NULL;
  label_ = NULL;
}

void TrayUser::DestroyDefaultView() {
  user_ = NULL;
}

void TrayUser::DestroyDetailedView() {
}

void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) {
  // Only the active user is represented in the tray.
  if (!layout_view_)
    return;
  if (GetTrayIndex() > 0 && !ash::switches::UseMultiUserTray())
    return;
  bool need_label = false;
  bool need_avatar = false;
  switch (status) {
    case user::LOGGED_IN_LOCKED:
    case user::LOGGED_IN_USER:
    case user::LOGGED_IN_OWNER:
    case user::LOGGED_IN_PUBLIC:
      need_avatar = true;
      break;
    case user::LOGGED_IN_LOCALLY_MANAGED:
      need_avatar = true;
      need_label = true;
      break;
    case user::LOGGED_IN_GUEST:
      need_label = true;
      break;
    case user::LOGGED_IN_RETAIL_MODE:
    case user::LOGGED_IN_KIOSK_APP:
    case user::LOGGED_IN_NONE:
      break;
  }

  if ((need_avatar != (avatar_ != NULL)) ||
      (need_label != (label_ != NULL))) {
    layout_view_->RemoveAllChildViews(true);
    if (need_label) {
      label_ = new views::Label;
      SetupLabelForTray(label_);
      layout_view_->AddChildView(label_);
    } else {
      label_ = NULL;
    }
    if (need_avatar) {
      MultiProfileIndex tray_index = GetTrayIndex();
      if (!tray_index) {
        // The active user (index #0) will always be the first.
        avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true);
      } else {
        // All other users will be inactive users.
        avatar_ = new tray::UserSwitcherView(kProfileRoundedCornerRadius,
                                             tray_index);
      }
      layout_view_->AddChildView(avatar_);
    } else {
      avatar_ = NULL;
    }
  }

  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
  if (status == user::LOGGED_IN_LOCALLY_MANAGED) {
    label_->SetText(
        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL));
  } else if (status == user::LOGGED_IN_GUEST) {
    label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
  }

  if (avatar_ && switches::UseAlternateShelfLayout()) {
    int corner_radius = GetTrayItemRadius();
    avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0);
    avatar_->set_border(NULL);
  }
  UpdateAvatarImage(status);

  // Update layout after setting label_ and avatar_ with new login status.
  UpdateLayoutOfItem();
}

void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
  // Inactive users won't have a layout.
  if (!layout_view_)
    return;
  int corner_radius = GetTrayItemRadius();
  if (alignment == SHELF_ALIGNMENT_BOTTOM ||
      alignment == SHELF_ALIGNMENT_TOP) {
    if (avatar_) {
      if (switches::UseAlternateShelfLayout()) {
        if (multiprofile_index_) {
          avatar_->set_border(
              views::Border::CreateEmptyBorder(0, kTrayLabelSpacing, 0, 0));
        } else {
          avatar_->set_border(NULL);
        }
        avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0);
      } else {
        avatar_->set_border(views::Border::CreateEmptyBorder(
            0, kTrayImageItemHorizontalPaddingBottomAlignment + 2,
            0, kTrayImageItemHorizontalPaddingBottomAlignment));
      }
    }
    if (label_) {
      label_->set_border(views::Border::CreateEmptyBorder(
          0, kTrayLabelItemHorizontalPaddingBottomAlignment,
          0, kTrayLabelItemHorizontalPaddingBottomAlignment));
    }
    layout_view_->SetLayoutManager(
        new views::BoxLayout(views::BoxLayout::kHorizontal,
                             0, 0, kUserLabelToIconPadding));
  } else {
    if (avatar_) {
      if (switches::UseAlternateShelfLayout()) {
        if (multiprofile_index_) {
          avatar_->set_border(
              views::Border::CreateEmptyBorder(kTrayLabelSpacing, 0, 0, 0));
        } else {
          avatar_->set_border(NULL);
        }
        avatar_->SetCornerRadii(0, 0, corner_radius, corner_radius);
      } else {
        SetTrayImageItemBorder(avatar_, alignment);
      }
    }
    if (label_) {
      label_->set_border(views::Border::CreateEmptyBorder(
          kTrayLabelItemVerticalPaddingVerticalAlignment,
          kTrayLabelItemHorizontalPaddingBottomAlignment,
          kTrayLabelItemVerticalPaddingVerticalAlignment,
          kTrayLabelItemHorizontalPaddingBottomAlignment));
    }
    layout_view_->SetLayoutManager(
        new views::BoxLayout(views::BoxLayout::kVertical,
                             0, 0, kUserLabelToIconPadding));
  }
}

void TrayUser::OnUserUpdate() {
  UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()->
      GetUserLoginStatus());
}

void TrayUser::OnUserAddedToSession() {
  SessionStateDelegate* session_state_delegate =
      Shell::GetInstance()->session_state_delegate();
  // Only create views for user items which are logged in.
  if (GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
    return;

  // Enforce a layout change that newly added items become visible.
  UpdateLayoutOfItem();

  // Update the user item.
  UpdateAvatarImage(
      Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
}

void TrayUser::UpdateAvatarImage(user::LoginStatus status) {
  SessionStateDelegate* session_state_delegate =
      Shell::GetInstance()->session_state_delegate();
  if (!avatar_ ||
      GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
    return;

  int icon_size = switches::UseAlternateShelfLayout() ?
      kUserIconLargeSize : kUserIconSize;

  avatar_->SetImage(
      Shell::GetInstance()->session_state_delegate()->GetUserImage(
          GetTrayIndex()),
      gfx::Size(icon_size, icon_size));

  // Unit tests might come here with no images for some users.
  if (avatar_->size().IsEmpty())
    avatar_->SetSize(gfx::Size(icon_size, icon_size));
}

MultiProfileIndex TrayUser::GetTrayIndex() {
  Shell* shell = Shell::GetInstance();
  // If multi profile is not enabled we can use the normal index.
  if (!shell->delegate()->IsMultiProfilesEnabled())
    return multiprofile_index_;
  // In case of multi profile we need to mirror the indices since the system
  // tray items are in the reverse order then the menu items.
  return shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() -
             1 - multiprofile_index_;
}

int TrayUser::GetTrayItemRadius() {
  SessionStateDelegate* delegate =
      Shell::GetInstance()->session_state_delegate();
  bool is_last_item = GetTrayIndex() == (delegate->NumberOfLoggedInUsers() - 1);
  return is_last_item ? kUserIconLargeCornerRadius : 0;
}

void TrayUser::UpdateLayoutOfItem() {
  internal::RootWindowController* controller =
      internal::GetRootWindowController(
          system_tray()->GetWidget()->GetNativeWindow()->GetRootWindow());
  if (controller && controller->shelf()) {
    UpdateAfterShelfAlignmentChange(
        controller->GetShelfLayoutManager()->GetAlignment());
  }
}

}  // namespace internal
}  // namespace ash