• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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|.
SwitchUser(ash::MultiProfileIndex 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);
~UserSwitcherView()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.
is_hovered_for_test()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:
UserViewMouseWatcherHost(const gfx::Rect & screen_area)262  explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
263      : screen_area_(screen_area) {}
~UserViewMouseWatcherHost()264  virtual ~UserViewMouseWatcherHost() {}
265 
266  // Implementation of MouseWatcherHost.
Contains(const gfx::Point & screen_point,views::MouseWatcherHost::MouseEventType type)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.
anchor()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 
RoundedImageView(int corner_radius,bool active_user)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 
~RoundedImageView()393 RoundedImageView::~RoundedImageView() {}
394 
SetImage(const gfx::ImageSkia & img,const gfx::Size & size)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 
SetCornerRadii(int top_left,int top_right,int bottom_right,int bottom_left)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 
GetPreferredSize()419 gfx::Size RoundedImageView::GetPreferredSize() {
420   return gfx::Size(image_size_.width() + GetInsets().width(),
421                    image_size_.height() + GetInsets().height());
422 }
423 
OnPaint(gfx::Canvas * canvas)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 
UserSwitcherView(int corner_radius,MultiProfileIndex user_index)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 
OnMouseEvent(ui::MouseEvent * event)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 
OnTouchEvent(ui::TouchEvent * event)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 
PublicAccountUserDetails(SystemTrayItem * owner,int used_width)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 
~PublicAccountUserDetails()504 PublicAccountUserDetails::~PublicAccountUserDetails() {}
505 
Layout()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 
GetPreferredSize()573 gfx::Size PublicAccountUserDetails::GetPreferredSize() {
574   return preferred_size_;
575 }
576 
OnPaint(gfx::Canvas * canvas)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 
LinkClicked(views::Link * source,int event_flags)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 
CalculatePreferredSize(SystemTrayItem * owner,int used_width)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 
UserCard(views::ButtonListener * listener,bool active_user)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 
~UserCard()659 UserCard::~UserCard() {}
660 
ForceBorderVisible(bool show)661 void UserCard::ForceBorderVisible(bool show) {
662   show_border_ = show;
663   ShowActive();
664 }
665 
OnMouseEntered(const ui::MouseEvent & event)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 
OnMouseExited(const ui::MouseEvent & event)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 
ShowActive()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 
UserView(SystemTrayItem * owner,user::LoginStatus login,MultiProfileIndex index)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 
~UserView()716 UserView::~UserView() {}
717 
MouseMovedOutOfHost()718 void UserView::MouseMovedOutOfHost() {
719   popup_message_.reset();
720   mouse_watcher_.reset();
721   add_menu_option_.reset();
722 }
723 
GetStateForTest() const724 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 
GetBoundsInScreenOfUserButtonForTest()737 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
738   DCHECK(user_card_view_);
739   return user_card_view_->GetBoundsInScreen();
740 }
741 
GetPreferredSize()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 
GetHeightForWidth(int width)752 int UserView::GetHeightForWidth(int width) {
753   return GetPreferredSize().height();
754 }
755 
Layout()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 
ButtonPressed(views::Button * sender,const ui::Event & event)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 
AddLogoutButton(user::LoginStatus login)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 
AddUserCard(SystemTrayItem * owner,user::LoginStatus login)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 
CreateIconForUserCard(user::LoginStatus login)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 
AddLoggedInRetailModeUserCardContent()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 
AddLoggedInPublicModeUserCardContent(SystemTrayItem * owner)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 
ToggleAddUserMenuOption()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 
SupportsMultiProfile()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 
AddUserView(UserCard * owner,views::ButtonListener * listener)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 
~AddUserView()1062 AddUserView::~AddUserView() {
1063   owner_->ForceBorderVisible(false);
1064 }
1065 
GetPreferredSize()1066 gfx::Size AddUserView::GetPreferredSize() {
1067   return owner_->bounds().size();
1068 }
1069 
GetHeightForWidth(int width)1070 int AddUserView::GetHeightForWidth(int width) {
1071   return owner_->bounds().size().height();
1072 }
1073 
Layout()1074 void AddUserView::Layout() {
1075   gfx::Rect contents_area(GetContentsBounds());
1076   add_user_->SetBoundsRect(contents_area);
1077 }
1078 
ButtonPressed(views::Button * sender,const ui::Event & event)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 
AddContent()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 
TrayUser(SystemTray * system_tray,MultiProfileIndex index)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 
~TrayUser()1140 TrayUser::~TrayUser() {
1141   Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this);
1142 }
1143 
GetStateForTest() const1144 TrayUser::TestState TrayUser::GetStateForTest() const {
1145   if (!user_)
1146     return HIDDEN;
1147   return user_->GetStateForTest();
1148 }
1149 
CanDropWindowHereToTransferToUser(const gfx::Point & point_in_screen)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 
TransferWindowToUser(aura::Window * window)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 
GetUserPanelBoundsInScreenForTest() const1167 gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const {
1168   DCHECK(user_);
1169   return user_->GetBoundsInScreenOfUserButtonForTest();
1170 }
1171 
CreateTrayView(user::LoginStatus status)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 
CreateDefaultView(user::LoginStatus status)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 
CreateDetailedView(user::LoginStatus status)1209 views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
1210   return NULL;
1211 }
1212 
DestroyTrayView()1213 void TrayUser::DestroyTrayView() {
1214   layout_view_ = NULL;
1215   avatar_ = NULL;
1216   label_ = NULL;
1217 }
1218 
DestroyDefaultView()1219 void TrayUser::DestroyDefaultView() {
1220   user_ = NULL;
1221 }
1222 
DestroyDetailedView()1223 void TrayUser::DestroyDetailedView() {
1224 }
1225 
UpdateAfterLoginStatusChange(user::LoginStatus status)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 
UpdateAfterShelfAlignmentChange(ShelfAlignment alignment)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 
OnUserUpdate()1356 void TrayUser::OnUserUpdate() {
1357   UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()->
1358       GetUserLoginStatus());
1359 }
1360 
OnUserAddedToSession()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 
UpdateAvatarImage(user::LoginStatus status)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 
GetTrayIndex()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 
GetTrayItemRadius()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 
UpdateLayoutOfItem()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