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/tray/tray_utils.h"
16 #include "ash/system/user/config.h"
17 #include "ash/system/user/rounded_image_view.h"
18 #include "base/i18n/rtl.h"
19 #include "base/memory/scoped_vector.h"
20 #include "base/strings/string16.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "components/user_manager/user_info.h"
24 #include "grit/ash_resources.h"
25 #include "grit/ash_strings.h"
26 #include "ui/accessibility/ax_view_state.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/resource/resource_bundle.h"
29 #include "ui/gfx/geometry/insets.h"
30 #include "ui/gfx/geometry/rect.h"
31 #include "ui/gfx/geometry/size.h"
32 #include "ui/gfx/range/range.h"
33 #include "ui/gfx/render_text.h"
34 #include "ui/gfx/text_elider.h"
35 #include "ui/gfx/text_utils.h"
36 #include "ui/views/border.h"
37 #include "ui/views/controls/link.h"
38 #include "ui/views/controls/link_listener.h"
39 #include "ui/views/layout/box_layout.h"
41 #if defined(OS_CHROMEOS)
42 #include "ash/ash_view_ids.h"
43 #include "ash/media_delegate.h"
44 #include "ash/system/tray/media_security/media_capture_observer.h"
45 #include "ui/views/controls/image_view.h"
46 #include "ui/views/layout/fill_layout.h"
54 const int kUserDetailsVerticalPadding
= 5;
56 // The invisible word joiner character, used as a marker to indicate the start
57 // and end of the user's display name in the public account user card's text.
58 const base::char16 kDisplayNameMark
[] = {0x2060, 0};
60 #if defined(OS_CHROMEOS)
61 class MediaIndicator
: public views::View
, public MediaCaptureObserver
{
63 explicit MediaIndicator(MultiProfileIndex index
)
64 : index_(index
), label_(new views::Label
) {
65 SetLayoutManager(new views::FillLayout
);
66 views::ImageView
* icon
= new views::ImageView
;
67 icon
->SetImage(ui::ResourceBundle::GetSharedInstance()
68 .GetImageNamed(IDR_AURA_UBER_TRAY_RECORDING_RED
)
71 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
72 label_
->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
73 ui::ResourceBundle::SmallFont
));
74 OnMediaCaptureChanged();
75 Shell::GetInstance()->system_tray_notifier()->AddMediaCaptureObserver(this);
76 set_id(VIEW_ID_USER_VIEW_MEDIA_INDICATOR
);
79 ~MediaIndicator() override
{
80 Shell::GetInstance()->system_tray_notifier()->RemoveMediaCaptureObserver(
84 // MediaCaptureObserver:
85 void OnMediaCaptureChanged() override
{
86 Shell
* shell
= Shell::GetInstance();
87 content::BrowserContext
* context
=
88 shell
->session_state_delegate()->GetBrowserContextByIndex(index_
);
89 MediaCaptureState state
=
90 Shell::GetInstance()->media_delegate()->GetMediaCaptureState(context
);
93 case MEDIA_CAPTURE_AUDIO_VIDEO
:
94 res_id
= IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO_VIDEO
;
96 case MEDIA_CAPTURE_AUDIO
:
97 res_id
= IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO
;
99 case MEDIA_CAPTURE_VIDEO
:
100 res_id
= IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_VIDEO
;
102 case MEDIA_CAPTURE_NONE
:
105 SetMessage(res_id
? l10n_util::GetStringUTF16(res_id
) : base::string16());
108 views::View
* GetMessageView() { return label_
; }
110 void SetMessage(const base::string16
& message
) {
111 SetVisible(!message
.empty());
112 label_
->SetText(message
);
113 label_
->SetVisible(!message
.empty());
117 MultiProfileIndex index_
;
118 views::Label
* label_
;
120 DISALLOW_COPY_AND_ASSIGN(MediaIndicator
);
124 // The user details shown in public account mode. This is essentially a label
125 // but with custom painting code as the text is styled with multiple colors and
127 class PublicAccountUserDetails
: public views::View
,
128 public views::LinkListener
{
130 PublicAccountUserDetails(int max_width
);
131 ~PublicAccountUserDetails() override
;
134 // Overridden from views::View.
135 void Layout() override
;
136 gfx::Size
GetPreferredSize() const override
;
137 void OnPaint(gfx::Canvas
* canvas
) override
;
139 // Overridden from views::LinkListener.
140 void LinkClicked(views::Link
* source
, int event_flags
) override
;
142 // Calculate a preferred size that ensures the label text and the following
143 // link do not wrap over more than three lines in total for aesthetic reasons
145 void CalculatePreferredSize(int max_allowed_width
);
147 base::string16 text_
;
148 views::Link
* learn_more_
;
149 gfx::Size preferred_size_
;
150 ScopedVector
<gfx::RenderText
> lines_
;
152 DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails
);
155 PublicAccountUserDetails::PublicAccountUserDetails(int max_width
)
156 : learn_more_(NULL
) {
157 const int inner_padding
=
158 kTrayPopupPaddingHorizontal
- kTrayPopupPaddingBetweenItems
;
159 const bool rtl
= base::i18n::IsRTL();
160 SetBorder(views::Border::CreateEmptyBorder(kUserDetailsVerticalPadding
,
161 rtl
? 0 : inner_padding
,
162 kUserDetailsVerticalPadding
,
163 rtl
? inner_padding
: 0));
165 // Retrieve the user's display name and wrap it with markers.
166 // Note that since this is a public account it always has to be the primary
168 base::string16 display_name
= Shell::GetInstance()
169 ->session_state_delegate()
172 base::RemoveChars(display_name
, kDisplayNameMark
, &display_name
);
173 display_name
= kDisplayNameMark
[0] + display_name
+ kDisplayNameMark
[0];
174 // Retrieve the domain managing the device and wrap it with markers.
175 base::string16 domain
= base::UTF8ToUTF16(
176 Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain());
177 base::RemoveChars(domain
, kDisplayNameMark
, &domain
);
178 base::i18n::WrapStringWithLTRFormatting(&domain
);
179 // Retrieve the label text, inserting the display name and domain.
180 text_
= l10n_util::GetStringFUTF16(
181 IDS_ASH_STATUS_TRAY_PUBLIC_LABEL
, display_name
, domain
);
183 learn_more_
= new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE
));
184 learn_more_
->SetUnderline(false);
185 learn_more_
->set_listener(this);
186 AddChildView(learn_more_
);
188 CalculatePreferredSize(max_width
);
191 PublicAccountUserDetails::~PublicAccountUserDetails() {}
193 void PublicAccountUserDetails::Layout() {
195 const gfx::Rect contents_area
= GetContentsBounds();
196 if (contents_area
.IsEmpty())
199 // Word-wrap the label text.
200 const gfx::FontList font_list
;
201 std::vector
<base::string16
> lines
;
202 gfx::ElideRectangleText(text_
,
204 contents_area
.width(),
205 contents_area
.height(),
206 gfx::ELIDE_LONG_WORDS
,
208 // Loop through the lines, creating a renderer for each.
209 gfx::Point position
= contents_area
.origin();
210 gfx::Range
display_name(gfx::Range::InvalidRange());
211 for (std::vector
<base::string16
>::const_iterator it
= lines
.begin();
214 gfx::RenderText
* line
= gfx::RenderText::CreateInstance();
215 line
->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI
);
217 const gfx::Size
size(contents_area
.width(), line
->GetStringSize().height());
218 line
->SetDisplayRect(gfx::Rect(position
, size
));
219 position
.set_y(position
.y() + size
.height());
221 // Set the default text color for the line.
222 line
->SetColor(kPublicAccountUserCardTextColor
);
224 // If a range of the line contains the user's display name, apply a custom
226 if (display_name
.is_empty())
227 display_name
.set_start(it
->find(kDisplayNameMark
));
228 if (!display_name
.is_empty()) {
229 display_name
.set_end(
230 it
->find(kDisplayNameMark
, display_name
.start() + 1));
231 gfx::Range
line_range(0, it
->size());
232 line
->ApplyColor(kPublicAccountUserCardNameColor
,
233 display_name
.Intersect(line_range
));
234 // Update the range for the next line.
235 if (display_name
.end() >= line_range
.end())
236 display_name
.set_start(0);
238 display_name
= gfx::Range::InvalidRange();
241 lines_
.push_back(line
);
244 // Position the link after the label text, separated by a space. If it does
245 // not fit onto the last line of the text, wrap the link onto its own line.
246 const gfx::Size last_line_size
= lines_
.back()->GetStringSize();
247 const int space_width
=
248 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list
);
249 const gfx::Size link_size
= learn_more_
->GetPreferredSize();
250 if (contents_area
.width() - last_line_size
.width() >=
251 space_width
+ link_size
.width()) {
252 position
.set_x(position
.x() + last_line_size
.width() + space_width
);
253 position
.set_y(position
.y() - last_line_size
.height());
255 position
.set_y(position
.y() - learn_more_
->GetInsets().top());
256 gfx::Rect
learn_more_bounds(position
, link_size
);
257 learn_more_bounds
.Intersect(contents_area
);
258 if (base::i18n::IsRTL()) {
259 const gfx::Insets insets
= GetInsets();
260 learn_more_bounds
.Offset(insets
.right() - insets
.left(), 0);
262 learn_more_
->SetBoundsRect(learn_more_bounds
);
265 gfx::Size
PublicAccountUserDetails::GetPreferredSize() const {
266 return preferred_size_
;
269 void PublicAccountUserDetails::OnPaint(gfx::Canvas
* canvas
) {
270 for (ScopedVector
<gfx::RenderText
>::const_iterator it
= lines_
.begin();
275 views::View::OnPaint(canvas
);
278 void PublicAccountUserDetails::LinkClicked(views::Link
* source
,
280 DCHECK_EQ(source
, learn_more_
);
281 Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo();
284 void PublicAccountUserDetails::CalculatePreferredSize(int max_allowed_width
) {
285 const gfx::FontList font_list
;
286 const gfx::Size link_size
= learn_more_
->GetPreferredSize();
287 const int space_width
=
288 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list
);
289 const gfx::Insets insets
= GetInsets();
290 int min_width
= link_size
.width();
291 int max_width
= std::min(
292 gfx::GetStringWidth(text_
, font_list
) + space_width
+ link_size
.width(),
293 max_allowed_width
- insets
.width());
294 // Do a binary search for the minimum width that ensures no more than three
295 // lines are needed. The lower bound is the minimum of the current bubble
296 // width and the width of the link (as no wrapping is permitted inside the
297 // link). The upper bound is the maximum of the largest allowed bubble width
298 // and the sum of the label text and link widths when put on a single line.
299 std::vector
<base::string16
> lines
;
300 while (min_width
< max_width
) {
302 const int width
= (min_width
+ max_width
) / 2;
303 const bool too_narrow
= gfx::ElideRectangleText(text_
,
307 gfx::TRUNCATE_LONG_WORDS
,
309 int line_count
= lines
.size();
310 if (!too_narrow
&& line_count
== 3 &&
311 width
- gfx::GetStringWidth(lines
.back(), font_list
) <=
312 space_width
+ link_size
.width())
314 if (too_narrow
|| line_count
> 3)
315 min_width
= width
+ 1;
320 // Calculate the corresponding height and set the preferred size.
322 gfx::ElideRectangleText(
323 text_
, font_list
, min_width
, INT_MAX
, gfx::TRUNCATE_LONG_WORDS
, &lines
);
324 int line_count
= lines
.size();
325 if (min_width
- gfx::GetStringWidth(lines
.back(), font_list
) <=
326 space_width
+ link_size
.width()) {
329 const int line_height
= font_list
.GetHeight();
330 const int link_extra_height
= std::max(
331 link_size
.height() - learn_more_
->GetInsets().top() - line_height
, 0);
333 gfx::Size(min_width
+ insets
.width(),
334 line_count
* line_height
+ link_extra_height
+ insets
.height());
339 UserCardView::UserCardView(user::LoginStatus login_status
,
341 int multiprofile_index
) {
342 SetLayoutManager(new views::BoxLayout(
343 views::BoxLayout::kHorizontal
, 0, 0, kTrayPopupPaddingBetweenItems
));
344 if (login_status
== user::LOGGED_IN_PUBLIC
) {
345 AddPublicModeUserContent(max_width
);
347 AddUserContent(login_status
, multiprofile_index
);
351 UserCardView::~UserCardView() {}
353 void UserCardView::GetAccessibleState(ui::AXViewState
* state
) {
354 state
->role
= ui::AX_ROLE_STATIC_TEXT
;
355 std::vector
<base::string16
> labels
;
356 for (int i
= 0; i
< child_count(); ++i
)
357 GetAccessibleLabelFromDescendantViews(child_at(i
), labels
);
358 state
->name
= JoinString(labels
, base::ASCIIToUTF16(" "));
361 void UserCardView::AddPublicModeUserContent(int max_width
) {
362 views::View
* icon
= CreateIcon(user::LOGGED_IN_PUBLIC
, 0);
364 int details_max_width
= max_width
- icon
->GetPreferredSize().width() -
365 kTrayPopupPaddingBetweenItems
;
366 AddChildView(new PublicAccountUserDetails(details_max_width
));
369 void UserCardView::AddUserContent(user::LoginStatus login_status
,
370 int multiprofile_index
) {
371 views::View
* icon
= CreateIcon(login_status
, multiprofile_index
);
373 views::Label
* user_name
= NULL
;
374 SessionStateDelegate
* delegate
=
375 Shell::GetInstance()->session_state_delegate();
376 if (!multiprofile_index
) {
377 base::string16 user_name_string
=
378 login_status
== user::LOGGED_IN_GUEST
379 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL
)
380 : delegate
->GetUserInfo(multiprofile_index
)->GetDisplayName();
381 if (user_name_string
.empty() && IsMultiAccountSupportedAndUserActive())
382 user_name_string
= base::ASCIIToUTF16(
383 delegate
->GetUserInfo(multiprofile_index
)->GetEmail());
384 if (!user_name_string
.empty()) {
385 user_name
= new views::Label(user_name_string
);
386 user_name
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
390 views::Label
* user_email
= NULL
;
391 if (login_status
!= user::LOGGED_IN_GUEST
&&
392 (multiprofile_index
|| !IsMultiAccountSupportedAndUserActive())) {
393 SystemTrayDelegate
* tray_delegate
=
394 Shell::GetInstance()->system_tray_delegate();
395 base::string16 user_email_string
=
396 tray_delegate
->IsUserSupervised()
397 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL
)
399 delegate
->GetUserInfo(multiprofile_index
)->GetEmail());
400 if (!user_email_string
.empty()) {
401 user_email
= new views::Label(user_email_string
);
402 user_email
->SetFontList(
403 ui::ResourceBundle::GetSharedInstance().GetFontList(
404 ui::ResourceBundle::SmallFont
));
405 user_email
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
409 // Adjust text properties dependent on if it is an active or inactive user.
410 if (multiprofile_index
) {
411 // Fade the text of non active users to 50%.
412 SkColor text_color
= user_email
->enabled_color();
413 text_color
= SkColorSetA(text_color
, SkColorGetA(text_color
) / 2);
415 user_email
->SetDisabledColor(text_color
);
417 user_name
->SetDisabledColor(text_color
);
420 if (user_email
&& user_name
) {
421 views::View
* details
= new views::View
;
422 details
->SetLayoutManager(new views::BoxLayout(
423 views::BoxLayout::kVertical
, 0, kUserDetailsVerticalPadding
, 0));
424 details
->AddChildView(user_name
);
425 details
->AddChildView(user_email
);
426 AddChildView(details
);
429 AddChildView(user_name
);
431 #if defined(OS_CHROMEOS)
432 // Only non active user can have a media indicator.
433 MediaIndicator
* media_indicator
= new MediaIndicator(multiprofile_index
);
434 views::View
* email_indicator_view
= new views::View
;
435 email_indicator_view
->SetLayoutManager(new views::BoxLayout(
436 views::BoxLayout::kHorizontal
, 0, 0, kTrayPopupPaddingBetweenItems
));
437 email_indicator_view
->AddChildView(user_email
);
438 email_indicator_view
->AddChildView(media_indicator
);
440 views::View
* details
= new views::View
;
441 details
->SetLayoutManager(new views::BoxLayout(
442 views::BoxLayout::kVertical
, 0, kUserDetailsVerticalPadding
, 0));
443 details
->AddChildView(email_indicator_view
);
444 details
->AddChildView(media_indicator
->GetMessageView());
445 AddChildView(details
);
447 AddChildView(user_email
);
453 views::View
* UserCardView::CreateIcon(user::LoginStatus login_status
,
454 int multiprofile_index
) {
455 RoundedImageView
* icon
=
456 new RoundedImageView(kTrayAvatarCornerRadius
, multiprofile_index
== 0);
457 if (login_status
== user::LOGGED_IN_GUEST
) {
458 icon
->SetImage(*ui::ResourceBundle::GetSharedInstance()
459 .GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON
)
461 gfx::Size(kTrayAvatarSize
, kTrayAvatarSize
));
463 SessionStateDelegate
* delegate
=
464 Shell::GetInstance()->session_state_delegate();
465 content::BrowserContext
* context
=
466 delegate
->GetBrowserContextByIndex(multiprofile_index
);
467 icon
->SetImage(delegate
->GetUserInfo(context
)->GetImage(),
468 gfx::Size(kTrayAvatarSize
, kTrayAvatarSize
));