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