1 // Copyright 2014 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.
5 #include "ash/system/user/user_card_view.h"
10 #include "ash/session/session_state_delegate.h"
11 #include "ash/shell.h"
12 #include "ash/system/tray/system_tray_delegate.h"
13 #include "ash/system/tray/system_tray_notifier.h"
14 #include "ash/system/tray/tray_constants.h"
15 #include "ash/system/user/config.h"
16 #include "ash/system/user/rounded_image_view.h"
17 #include "base/i18n/rtl.h"
18 #include "base/memory/scoped_vector.h"
19 #include "base/strings/string16.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "components/user_manager/user_info.h"
23 #include "grit/ash_resources.h"
24 #include "grit/ash_strings.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/insets.h"
28 #include "ui/gfx/range/range.h"
29 #include "ui/gfx/rect.h"
30 #include "ui/gfx/render_text.h"
31 #include "ui/gfx/size.h"
32 #include "ui/gfx/text_elider.h"
33 #include "ui/gfx/text_utils.h"
34 #include "ui/views/border.h"
35 #include "ui/views/controls/link.h"
36 #include "ui/views/controls/link_listener.h"
37 #include "ui/views/layout/box_layout.h"
39 #if defined(OS_CHROMEOS)
40 #include "ash/ash_view_ids.h"
41 #include "ash/media_delegate.h"
42 #include "ash/system/tray/media_security/media_capture_observer.h"
43 #include "ui/views/controls/image_view.h"
44 #include "ui/views/layout/fill_layout.h"
52 const int kUserDetailsVerticalPadding
= 5;
54 // The invisible word joiner character, used as a marker to indicate the start
55 // and end of the user's display name in the public account user card's text.
56 const base::char16 kDisplayNameMark
[] = {0x2060, 0};
58 #if defined(OS_CHROMEOS)
59 class MediaIndicator
: public views::View
, public MediaCaptureObserver
{
61 explicit MediaIndicator(MultiProfileIndex index
)
62 : index_(index
), label_(new views::Label
) {
63 SetLayoutManager(new views::FillLayout
);
64 views::ImageView
* icon
= new views::ImageView
;
65 icon
->SetImage(ui::ResourceBundle::GetSharedInstance()
66 .GetImageNamed(IDR_AURA_UBER_TRAY_RECORDING_RED
)
69 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
70 label_
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
71 ui::ResourceBundle::SmallFont
));
72 OnMediaCaptureChanged();
73 Shell::GetInstance()->system_tray_notifier()->AddMediaCaptureObserver(this);
74 set_id(VIEW_ID_USER_VIEW_MEDIA_INDICATOR
);
77 virtual ~MediaIndicator() {
78 Shell::GetInstance()->system_tray_notifier()->RemoveMediaCaptureObserver(
82 // MediaCaptureObserver:
83 virtual void OnMediaCaptureChanged() OVERRIDE
{
84 Shell
* shell
= Shell::GetInstance();
85 content::BrowserContext
* context
=
86 shell
->session_state_delegate()->GetBrowserContextByIndex(index_
);
87 MediaCaptureState state
=
88 Shell::GetInstance()->media_delegate()->GetMediaCaptureState(context
);
91 case MEDIA_CAPTURE_AUDIO_VIDEO
:
92 res_id
= IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO_VIDEO
;
94 case MEDIA_CAPTURE_AUDIO
:
95 res_id
= IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO
;
97 case MEDIA_CAPTURE_VIDEO
:
98 res_id
= IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_VIDEO
;
100 case MEDIA_CAPTURE_NONE
:
103 SetMessage(res_id
? l10n_util::GetStringUTF16(res_id
) : base::string16());
106 views::View
* GetMessageView() { return label_
; }
108 void SetMessage(const base::string16
& message
) {
109 SetVisible(!message
.empty());
110 label_
->SetText(message
);
111 label_
->SetVisible(!message
.empty());
115 MultiProfileIndex index_
;
116 views::Label
* label_
;
118 DISALLOW_COPY_AND_ASSIGN(MediaIndicator
);
122 // The user details shown in public account mode. This is essentially a label
123 // but with custom painting code as the text is styled with multiple colors and
125 class PublicAccountUserDetails
: public views::View
,
126 public views::LinkListener
{
128 PublicAccountUserDetails(int max_width
);
129 virtual ~PublicAccountUserDetails();
132 // Overridden from views::View.
133 virtual void Layout() OVERRIDE
;
134 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
135 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
137 // Overridden from views::LinkListener.
138 virtual void LinkClicked(views::Link
* source
, int event_flags
) OVERRIDE
;
140 // Calculate a preferred size that ensures the label text and the following
141 // link do not wrap over more than three lines in total for aesthetic reasons
143 void CalculatePreferredSize(int max_allowed_width
);
145 base::string16 text_
;
146 views::Link
* learn_more_
;
147 gfx::Size preferred_size_
;
148 ScopedVector
<gfx::RenderText
> lines_
;
150 DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails
);
153 PublicAccountUserDetails::PublicAccountUserDetails(int max_width
)
154 : learn_more_(NULL
) {
155 const int inner_padding
=
156 kTrayPopupPaddingHorizontal
- kTrayPopupPaddingBetweenItems
;
157 const bool rtl
= base::i18n::IsRTL();
158 SetBorder(views::Border::CreateEmptyBorder(kUserDetailsVerticalPadding
,
159 rtl
? 0 : inner_padding
,
160 kUserDetailsVerticalPadding
,
161 rtl
? inner_padding
: 0));
163 // Retrieve the user's display name and wrap it with markers.
164 // Note that since this is a public account it always has to be the primary
166 base::string16 display_name
= Shell::GetInstance()
167 ->session_state_delegate()
170 base::RemoveChars(display_name
, kDisplayNameMark
, &display_name
);
171 display_name
= kDisplayNameMark
[0] + display_name
+ kDisplayNameMark
[0];
172 // Retrieve the domain managing the device and wrap it with markers.
173 base::string16 domain
= base::UTF8ToUTF16(
174 Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain());
175 base::RemoveChars(domain
, kDisplayNameMark
, &domain
);
176 base::i18n::WrapStringWithLTRFormatting(&domain
);
177 // Retrieve the label text, inserting the display name and domain.
178 text_
= l10n_util::GetStringFUTF16(
179 IDS_ASH_STATUS_TRAY_PUBLIC_LABEL
, display_name
, domain
);
181 learn_more_
= new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE
));
182 learn_more_
->SetUnderline(false);
183 learn_more_
->set_listener(this);
184 AddChildView(learn_more_
);
186 CalculatePreferredSize(max_width
);
189 PublicAccountUserDetails::~PublicAccountUserDetails() {}
191 void PublicAccountUserDetails::Layout() {
193 const gfx::Rect contents_area
= GetContentsBounds();
194 if (contents_area
.IsEmpty())
197 // Word-wrap the label text.
198 const gfx::FontList font_list
;
199 std::vector
<base::string16
> lines
;
200 gfx::ElideRectangleText(text_
,
202 contents_area
.width(),
203 contents_area
.height(),
204 gfx::ELIDE_LONG_WORDS
,
206 // Loop through the lines, creating a renderer for each.
207 gfx::Point position
= contents_area
.origin();
208 gfx::Range
display_name(gfx::Range::InvalidRange());
209 for (std::vector
<base::string16
>::const_iterator it
= lines
.begin();
212 gfx::RenderText
* line
= gfx::RenderText::CreateInstance();
213 line
->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI
);
215 const gfx::Size
size(contents_area
.width(), line
->GetStringSize().height());
216 line
->SetDisplayRect(gfx::Rect(position
, size
));
217 position
.set_y(position
.y() + size
.height());
219 // Set the default text color for the line.
220 line
->SetColor(kPublicAccountUserCardTextColor
);
222 // If a range of the line contains the user's display name, apply a custom
224 if (display_name
.is_empty())
225 display_name
.set_start(it
->find(kDisplayNameMark
));
226 if (!display_name
.is_empty()) {
227 display_name
.set_end(
228 it
->find(kDisplayNameMark
, display_name
.start() + 1));
229 gfx::Range
line_range(0, it
->size());
230 line
->ApplyColor(kPublicAccountUserCardNameColor
,
231 display_name
.Intersect(line_range
));
232 // Update the range for the next line.
233 if (display_name
.end() >= line_range
.end())
234 display_name
.set_start(0);
236 display_name
= gfx::Range::InvalidRange();
239 lines_
.push_back(line
);
242 // Position the link after the label text, separated by a space. If it does
243 // not fit onto the last line of the text, wrap the link onto its own line.
244 const gfx::Size last_line_size
= lines_
.back()->GetStringSize();
245 const int space_width
=
246 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list
);
247 const gfx::Size link_size
= learn_more_
->GetPreferredSize();
248 if (contents_area
.width() - last_line_size
.width() >=
249 space_width
+ link_size
.width()) {
250 position
.set_x(position
.x() + last_line_size
.width() + space_width
);
251 position
.set_y(position
.y() - last_line_size
.height());
253 position
.set_y(position
.y() - learn_more_
->GetInsets().top());
254 gfx::Rect
learn_more_bounds(position
, link_size
);
255 learn_more_bounds
.Intersect(contents_area
);
256 if (base::i18n::IsRTL()) {
257 const gfx::Insets insets
= GetInsets();
258 learn_more_bounds
.Offset(insets
.right() - insets
.left(), 0);
260 learn_more_
->SetBoundsRect(learn_more_bounds
);
263 gfx::Size
PublicAccountUserDetails::GetPreferredSize() const {
264 return preferred_size_
;
267 void PublicAccountUserDetails::OnPaint(gfx::Canvas
* canvas
) {
268 for (ScopedVector
<gfx::RenderText
>::const_iterator it
= lines_
.begin();
273 views::View::OnPaint(canvas
);
276 void PublicAccountUserDetails::LinkClicked(views::Link
* source
,
278 DCHECK_EQ(source
, learn_more_
);
279 Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo();
282 void PublicAccountUserDetails::CalculatePreferredSize(int max_allowed_width
) {
283 const gfx::FontList font_list
;
284 const gfx::Size link_size
= learn_more_
->GetPreferredSize();
285 const int space_width
=
286 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list
);
287 const gfx::Insets insets
= GetInsets();
288 int min_width
= link_size
.width();
289 int max_width
= std::min(
290 gfx::GetStringWidth(text_
, font_list
) + space_width
+ link_size
.width(),
291 max_allowed_width
- insets
.width());
292 // Do a binary search for the minimum width that ensures no more than three
293 // lines are needed. The lower bound is the minimum of the current bubble
294 // width and the width of the link (as no wrapping is permitted inside the
295 // link). The upper bound is the maximum of the largest allowed bubble width
296 // and the sum of the label text and link widths when put on a single line.
297 std::vector
<base::string16
> lines
;
298 while (min_width
< max_width
) {
300 const int width
= (min_width
+ max_width
) / 2;
301 const bool too_narrow
= gfx::ElideRectangleText(text_
,
305 gfx::TRUNCATE_LONG_WORDS
,
307 int line_count
= lines
.size();
308 if (!too_narrow
&& line_count
== 3 &&
309 width
- gfx::GetStringWidth(lines
.back(), font_list
) <=
310 space_width
+ link_size
.width())
312 if (too_narrow
|| line_count
> 3)
313 min_width
= width
+ 1;
318 // Calculate the corresponding height and set the preferred size.
320 gfx::ElideRectangleText(
321 text_
, font_list
, min_width
, INT_MAX
, gfx::TRUNCATE_LONG_WORDS
, &lines
);
322 int line_count
= lines
.size();
323 if (min_width
- gfx::GetStringWidth(lines
.back(), font_list
) <=
324 space_width
+ link_size
.width()) {
327 const int line_height
= font_list
.GetHeight();
328 const int link_extra_height
= std::max(
329 link_size
.height() - learn_more_
->GetInsets().top() - line_height
, 0);
331 gfx::Size(min_width
+ insets
.width(),
332 line_count
* line_height
+ link_extra_height
+ insets
.height());
337 UserCardView::UserCardView(user::LoginStatus login_status
,
339 int multiprofile_index
) {
340 SetLayoutManager(new views::BoxLayout(
341 views::BoxLayout::kHorizontal
, 0, 0, kTrayPopupPaddingBetweenItems
));
342 switch (login_status
) {
343 case user::LOGGED_IN_RETAIL_MODE
:
344 AddRetailModeUserContent();
346 case user::LOGGED_IN_PUBLIC
:
347 AddPublicModeUserContent(max_width
);
350 AddUserContent(login_status
, multiprofile_index
);
355 UserCardView::~UserCardView() {}
357 void UserCardView::AddRetailModeUserContent() {
358 views::Label
* details
= new views::Label
;
359 details
->SetText(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_KIOSK_LABEL
));
360 details
->SetBorder(views::Border::CreateEmptyBorder(0, 4, 0, 1));
361 details
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
362 AddChildView(details
);
365 void UserCardView::AddPublicModeUserContent(int max_width
) {
366 views::View
* icon
= CreateIcon(user::LOGGED_IN_PUBLIC
, 0);
368 int details_max_width
= max_width
- icon
->GetPreferredSize().width() -
369 kTrayPopupPaddingBetweenItems
;
370 AddChildView(new PublicAccountUserDetails(details_max_width
));
373 void UserCardView::AddUserContent(user::LoginStatus login_status
,
374 int multiprofile_index
) {
375 views::View
* icon
= CreateIcon(login_status
, multiprofile_index
);
377 views::Label
* user_name
= NULL
;
378 SessionStateDelegate
* delegate
=
379 Shell::GetInstance()->session_state_delegate();
380 if (!multiprofile_index
) {
381 base::string16 user_name_string
=
382 login_status
== user::LOGGED_IN_GUEST
383 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL
)
384 : delegate
->GetUserInfo(multiprofile_index
)->GetDisplayName();
385 if (user_name_string
.empty() && IsMultiAccountSupportedAndUserActive())
386 user_name_string
= base::ASCIIToUTF16(
387 delegate
->GetUserInfo(multiprofile_index
)->GetEmail());
388 if (!user_name_string
.empty()) {
389 user_name
= new views::Label(user_name_string
);
390 user_name
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
394 views::Label
* user_email
= NULL
;
395 if (login_status
!= user::LOGGED_IN_GUEST
&&
396 (multiprofile_index
|| !IsMultiAccountSupportedAndUserActive())) {
397 base::string16 user_email_string
=
398 login_status
== user::LOGGED_IN_SUPERVISED
399 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL
)
401 delegate
->GetUserInfo(multiprofile_index
)->GetEmail());
402 if (!user_email_string
.empty()) {
403 user_email
= new views::Label(user_email_string
);
404 user_email
->SetFontList(
405 ui::ResourceBundle::GetSharedInstance().GetFontList(
406 ui::ResourceBundle::SmallFont
));
407 user_email
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
411 // Adjust text properties dependent on if it is an active or inactive user.
412 if (multiprofile_index
) {
413 // Fade the text of non active users to 50%.
414 SkColor text_color
= user_email
->enabled_color();
415 text_color
= SkColorSetA(text_color
, SkColorGetA(text_color
) / 2);
417 user_email
->SetDisabledColor(text_color
);
419 user_name
->SetDisabledColor(text_color
);
422 if (user_email
&& user_name
) {
423 views::View
* details
= new views::View
;
424 details
->SetLayoutManager(new views::BoxLayout(
425 views::BoxLayout::kVertical
, 0, kUserDetailsVerticalPadding
, 0));
426 details
->AddChildView(user_name
);
427 details
->AddChildView(user_email
);
428 AddChildView(details
);
431 AddChildView(user_name
);
433 #if defined(OS_CHROMEOS)
434 // Only non active user can have a media indicator.
435 MediaIndicator
* media_indicator
= new MediaIndicator(multiprofile_index
);
436 views::View
* email_indicator_view
= new views::View
;
437 email_indicator_view
->SetLayoutManager(new views::BoxLayout(
438 views::BoxLayout::kHorizontal
, 0, 0, kTrayPopupPaddingBetweenItems
));
439 email_indicator_view
->AddChildView(user_email
);
440 email_indicator_view
->AddChildView(media_indicator
);
442 views::View
* details
= new views::View
;
443 details
->SetLayoutManager(new views::BoxLayout(
444 views::BoxLayout::kVertical
, 0, kUserDetailsVerticalPadding
, 0));
445 details
->AddChildView(email_indicator_view
);
446 details
->AddChildView(media_indicator
->GetMessageView());
447 AddChildView(details
);
449 AddChildView(user_email
);
455 views::View
* UserCardView::CreateIcon(user::LoginStatus login_status
,
456 int multiprofile_index
) {
457 RoundedImageView
* icon
=
458 new RoundedImageView(kTrayAvatarCornerRadius
, multiprofile_index
== 0);
459 if (login_status
== user::LOGGED_IN_GUEST
) {
460 icon
->SetImage(*ui::ResourceBundle::GetSharedInstance()
461 .GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON
)
463 gfx::Size(kTrayAvatarSize
, kTrayAvatarSize
));
465 SessionStateDelegate
* delegate
=
466 Shell::GetInstance()->session_state_delegate();
467 content::BrowserContext
* context
=
468 delegate
->GetBrowserContextByIndex(multiprofile_index
);
469 icon
->SetImage(delegate
->GetUserInfo(context
)->GetImage(),
470 gfx::Size(kTrayAvatarSize
, kTrayAvatarSize
));