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 "ash/ash_switches.h"
8#include "ash/root_window_controller.h"
9#include "ash/session/session_state_delegate.h"
10#include "ash/shelf/shelf_layout_manager.h"
11#include "ash/shell_delegate.h"
12#include "ash/system/tray/system_tray.h"
13#include "ash/system/tray/system_tray_delegate.h"
14#include "ash/system/tray/system_tray_notifier.h"
15#include "ash/system/tray/tray_constants.h"
16#include "ash/system/tray/tray_item_view.h"
17#include "ash/system/tray/tray_utils.h"
18#include "ash/system/user/accounts_detailed_view.h"
19#include "ash/system/user/rounded_image_view.h"
20#include "ash/system/user/user_view.h"
21#include "base/logging.h"
22#include "base/strings/string16.h"
23#include "components/user_manager/user_info.h"
24#include "grit/ash_strings.h"
25#include "ui/aura/window.h"
26#include "ui/base/l10n/l10n_util.h"
27#include "ui/gfx/image/image.h"
28#include "ui/views/border.h"
29#include "ui/views/controls/label.h"
30#include "ui/views/layout/box_layout.h"
31#include "ui/views/view.h"
32#include "ui/views/widget/widget.h"
33
34namespace {
35
36const int kUserLabelToIconPadding = 5;
37
38}  // namespace
39
40namespace ash {
41
42TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index)
43    : SystemTrayItem(system_tray),
44      multiprofile_index_(index),
45      user_(NULL),
46      layout_view_(NULL),
47      avatar_(NULL),
48      label_(NULL) {
49  Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this);
50}
51
52TrayUser::~TrayUser() {
53  Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this);
54}
55
56TrayUser::TestState TrayUser::GetStateForTest() const {
57  if (!user_)
58    return HIDDEN;
59  return user_->GetStateForTest();
60}
61
62gfx::Size TrayUser::GetLayoutSizeForTest() const {
63  if (!layout_view_) {
64    return gfx::Size(0, 0);
65  } else {
66    return layout_view_->size();
67  }
68}
69
70gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const {
71  DCHECK(user_);
72  return user_->GetBoundsInScreenOfUserButtonForTest();
73}
74
75void TrayUser::UpdateAfterLoginStatusChangeForTest(user::LoginStatus status) {
76  UpdateAfterLoginStatusChange(status);
77}
78
79views::View* TrayUser::CreateTrayView(user::LoginStatus status) {
80  CHECK(layout_view_ == NULL);
81
82  layout_view_ = new views::View;
83  layout_view_->SetLayoutManager(
84      new views::BoxLayout(views::BoxLayout::kHorizontal,
85                           0, 0, kUserLabelToIconPadding));
86  UpdateAfterLoginStatusChange(status);
87  return layout_view_;
88}
89
90views::View* TrayUser::CreateDefaultView(user::LoginStatus status) {
91  if (status == user::LOGGED_IN_NONE)
92    return NULL;
93  const SessionStateDelegate* session_state_delegate =
94      Shell::GetInstance()->session_state_delegate();
95
96  // If the screen is locked or a system modal dialog box is shown, show only
97  // the currently active user.
98  if (multiprofile_index_ &&
99      (session_state_delegate->IsUserSessionBlocked() ||
100       Shell::GetInstance()->IsSystemModalWindowOpen()))
101    return NULL;
102
103  CHECK(user_ == NULL);
104
105  int logged_in_users = session_state_delegate->NumberOfLoggedInUsers();
106
107  // Do not show more UserView's then there are logged in users.
108  if (multiprofile_index_ >= logged_in_users)
109    return NULL;
110
111  user_ = new tray::UserView(this, status, multiprofile_index_, false);
112  return user_;
113}
114
115views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
116  return new tray::AccountsDetailedView(this, status);
117}
118
119void TrayUser::DestroyTrayView() {
120  layout_view_ = NULL;
121  avatar_ = NULL;
122  label_ = NULL;
123}
124
125void TrayUser::DestroyDefaultView() {
126  user_ = NULL;
127}
128
129void TrayUser::DestroyDetailedView() {
130}
131
132void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) {
133  // Only the active user is represented in the tray.
134  if (!layout_view_)
135    return;
136  if (GetTrayIndex() > 0)
137    return;
138  bool need_label = false;
139  bool need_avatar = false;
140  SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
141  if (delegate->IsUserSupervised())
142    need_label =  true;
143  switch (status) {
144    case user::LOGGED_IN_LOCKED:
145    case user::LOGGED_IN_USER:
146    case user::LOGGED_IN_OWNER:
147    case user::LOGGED_IN_PUBLIC:
148      need_avatar = true;
149      break;
150    case user::LOGGED_IN_SUPERVISED:
151      need_avatar = true;
152      need_label = true;
153      break;
154    case user::LOGGED_IN_GUEST:
155      need_label = true;
156      break;
157    case user::LOGGED_IN_RETAIL_MODE:
158    case user::LOGGED_IN_KIOSK_APP:
159    case user::LOGGED_IN_NONE:
160      break;
161  }
162
163  if ((need_avatar != (avatar_ != NULL)) ||
164      (need_label != (label_ != NULL))) {
165    layout_view_->RemoveAllChildViews(true);
166    if (need_label) {
167      label_ = new views::Label;
168      SetupLabelForTray(label_);
169      layout_view_->AddChildView(label_);
170    } else {
171      label_ = NULL;
172    }
173    if (need_avatar) {
174      avatar_ = new tray::RoundedImageView(kTrayAvatarCornerRadius, true);
175      layout_view_->AddChildView(avatar_);
176    } else {
177      avatar_ = NULL;
178    }
179  }
180
181  if (delegate->IsUserSupervised()) {
182    label_->SetText(
183        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL));
184  } else if (status == user::LOGGED_IN_GUEST) {
185    label_->SetText(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
186  }
187
188  if (avatar_) {
189    avatar_->SetCornerRadii(
190        0, kTrayAvatarCornerRadius, kTrayAvatarCornerRadius, 0);
191    avatar_->SetBorder(views::Border::NullBorder());
192  }
193  UpdateAvatarImage(status);
194
195  // Update layout after setting label_ and avatar_ with new login status.
196  UpdateLayoutOfItem();
197}
198
199void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
200  // Inactive users won't have a layout.
201  if (!layout_view_)
202    return;
203  if (alignment == SHELF_ALIGNMENT_BOTTOM ||
204      alignment == SHELF_ALIGNMENT_TOP) {
205    if (avatar_) {
206      avatar_->SetBorder(views::Border::NullBorder());
207      avatar_->SetCornerRadii(
208          0, kTrayAvatarCornerRadius, kTrayAvatarCornerRadius, 0);
209    }
210    if (label_) {
211      // If label_ hasn't figured out its size yet, do that first.
212      if (label_->GetContentsBounds().height() == 0)
213        label_->SizeToPreferredSize();
214      int height = label_->GetContentsBounds().height();
215      int vertical_pad = (kTrayItemSize - height) / 2;
216      int remainder = height % 2;
217      label_->SetBorder(views::Border::CreateEmptyBorder(
218          vertical_pad + remainder,
219          kTrayLabelItemHorizontalPaddingBottomAlignment,
220          vertical_pad,
221          kTrayLabelItemHorizontalPaddingBottomAlignment));
222    }
223    layout_view_->SetLayoutManager(
224        new views::BoxLayout(views::BoxLayout::kHorizontal,
225                             0, 0, kUserLabelToIconPadding));
226  } else {
227    if (avatar_) {
228      avatar_->SetBorder(views::Border::NullBorder());
229      avatar_->SetCornerRadii(
230          0, 0, kTrayAvatarCornerRadius, kTrayAvatarCornerRadius);
231    }
232    if (label_) {
233      label_->SetBorder(views::Border::CreateEmptyBorder(
234          kTrayLabelItemVerticalPaddingVerticalAlignment,
235          kTrayLabelItemHorizontalPaddingBottomAlignment,
236          kTrayLabelItemVerticalPaddingVerticalAlignment,
237          kTrayLabelItemHorizontalPaddingBottomAlignment));
238    }
239    layout_view_->SetLayoutManager(
240        new views::BoxLayout(views::BoxLayout::kVertical,
241                             0, 0, kUserLabelToIconPadding));
242  }
243}
244
245void TrayUser::OnUserUpdate() {
246  UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()->
247      GetUserLoginStatus());
248}
249
250void TrayUser::OnUserAddedToSession() {
251  SessionStateDelegate* session_state_delegate =
252      Shell::GetInstance()->session_state_delegate();
253  // Only create views for user items which are logged in.
254  if (GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
255    return;
256
257  // Enforce a layout change that newly added items become visible.
258  UpdateLayoutOfItem();
259
260  // Update the user item.
261  UpdateAvatarImage(
262      Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
263}
264
265void TrayUser::UpdateAvatarImage(user::LoginStatus status) {
266  SessionStateDelegate* session_state_delegate =
267      Shell::GetInstance()->session_state_delegate();
268  if (!avatar_ ||
269      GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers())
270    return;
271
272  const user_manager::UserInfo* user_info =
273      session_state_delegate->GetUserInfo(GetTrayIndex());
274  CHECK(user_info);
275  avatar_->SetImage(user_info->GetImage(),
276                    gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
277
278  // Unit tests might come here with no images for some users.
279  if (avatar_->size().IsEmpty())
280    avatar_->SetSize(gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
281}
282
283MultiProfileIndex TrayUser::GetTrayIndex() {
284  Shell* shell = Shell::GetInstance();
285  // If multi profile is not enabled we can use the normal index.
286  if (!shell->delegate()->IsMultiProfilesEnabled())
287    return multiprofile_index_;
288  // In case of multi profile we need to mirror the indices since the system
289  // tray items are in the reverse order then the menu items.
290  return shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() -
291             1 - multiprofile_index_;
292}
293
294void TrayUser::UpdateLayoutOfItem() {
295  RootWindowController* controller = GetRootWindowController(
296      system_tray()->GetWidget()->GetNativeWindow()->GetRootWindow());
297  if (controller && controller->shelf()) {
298    UpdateAfterShelfAlignmentChange(
299        controller->GetShelfLayoutManager()->GetAlignment());
300  }
301}
302
303}  // namespace ash
304