Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ash / system / user / user_card_view.cc
blob5201688ee592cba234624a9848adffbaa46e1137
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"
7 #include <algorithm>
8 #include <vector>
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"
47 #endif
49 namespace ash {
50 namespace tray {
52 namespace {
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 {
62 public:
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)
69 .ToImageSkia());
70 AddChildView(icon);
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(
81 this);
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);
91 int res_id = 0;
92 switch (state) {
93 case MEDIA_CAPTURE_AUDIO_VIDEO:
94 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO_VIDEO;
95 break;
96 case MEDIA_CAPTURE_AUDIO:
97 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO;
98 break;
99 case MEDIA_CAPTURE_VIDEO:
100 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_VIDEO;
101 break;
102 case MEDIA_CAPTURE_NONE:
103 break;
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());
116 private:
117 MultiProfileIndex index_;
118 views::Label* label_;
120 DISALLOW_COPY_AND_ASSIGN(MediaIndicator);
122 #endif
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
126 // contains a link.
127 class PublicAccountUserDetails : public views::View,
128 public views::LinkListener {
129 public:
130 PublicAccountUserDetails(int max_width);
131 ~PublicAccountUserDetails() override;
133 private:
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
144 // if possible.
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
167 // user.
168 base::string16 display_name = Shell::GetInstance()
169 ->session_state_delegate()
170 ->GetUserInfo(0)
171 ->GetDisplayName();
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() {
194 lines_.clear();
195 const gfx::Rect contents_area = GetContentsBounds();
196 if (contents_area.IsEmpty())
197 return;
199 // Word-wrap the label text.
200 const gfx::FontList font_list;
201 std::vector<base::string16> lines;
202 gfx::ElideRectangleText(text_,
203 font_list,
204 contents_area.width(),
205 contents_area.height(),
206 gfx::ELIDE_LONG_WORDS,
207 &lines);
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();
212 it != lines.end();
213 ++it) {
214 gfx::RenderText* line = gfx::RenderText::CreateInstance();
215 line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
216 line->SetText(*it);
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
225 // text color to it.
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);
237 else
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();
271 it != lines_.end();
272 ++it) {
273 (*it)->Draw(canvas);
275 views::View::OnPaint(canvas);
278 void PublicAccountUserDetails::LinkClicked(views::Link* source,
279 int event_flags) {
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) {
301 lines.clear();
302 const int width = (min_width + max_width) / 2;
303 const bool too_narrow = gfx::ElideRectangleText(text_,
304 font_list,
305 width,
306 INT_MAX,
307 gfx::TRUNCATE_LONG_WORDS,
308 &lines) != 0;
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())
313 ++line_count;
314 if (too_narrow || line_count > 3)
315 min_width = width + 1;
316 else
317 max_width = width;
320 // Calculate the corresponding height and set the preferred size.
321 lines.clear();
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()) {
327 ++line_count;
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);
332 preferred_size_ =
333 gfx::Size(min_width + insets.width(),
334 line_count * line_height + link_extra_height + insets.height());
337 } // namespace
339 UserCardView::UserCardView(user::LoginStatus login_status,
340 int max_width,
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);
346 } else {
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 = base::JoinString(labels, base::ASCIIToUTF16(" "));
361 void UserCardView::AddPublicModeUserContent(int max_width) {
362 views::View* icon = CreateIcon(user::LOGGED_IN_PUBLIC, 0);
363 AddChildView(icon);
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);
372 AddChildView(icon);
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)
398 : base::UTF8ToUTF16(
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);
414 if (user_email)
415 user_email->SetDisabledColor(text_color);
416 if (user_name)
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);
427 } else {
428 if (user_name)
429 AddChildView(user_name);
430 if (user_email) {
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);
446 #else
447 AddChildView(user_email);
448 #endif
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)
460 .ToImageSkia(),
461 gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
462 } else {
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));
470 return icon;
473 } // namespace tray
474 } // namespace ash