Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(422)

Side by Side Diff: ash/system/user/user_view.cc

Issue 2125373002: mash: Migrate ash/system/user to ash/common. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Sync and rebase. Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« ash/common/ash_view_ids.h ('K') | « ash/system/user/user_view.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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.
4
5 #include "ash/system/user/user_view.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "ash/common/multi_profile_uma.h"
11 #include "ash/common/popup_message.h"
12 #include "ash/common/session/session_state_delegate.h"
13 #include "ash/common/shell_delegate.h"
14 #include "ash/common/shell_window_ids.h"
15 #include "ash/common/system/tray/system_tray_delegate.h"
16 #include "ash/common/system/tray/tray_constants.h"
17 #include "ash/common/system/tray/tray_popup_label_button.h"
18 #include "ash/common/system/tray/tray_popup_label_button_border.h"
19 #include "ash/common/system/user/button_from_view.h"
20 #include "ash/common/system/user/login_status.h"
21 #include "ash/common/system/user/rounded_image_view.h"
22 #include "ash/common/wm_lookup.h"
23 #include "ash/common/wm_root_window_controller.h"
24 #include "ash/common/wm_shell.h"
25 #include "ash/common/wm_window.h"
26 #include "ash/system/tray/system_tray.h"
27 #include "ash/system/user/user_card_view.h"
28 #include "components/signin/core/account_id/account_id.h"
29 #include "components/user_manager/user_info.h"
30 #include "grit/ash_resources.h"
31 #include "grit/ash_strings.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/views/layout/fill_layout.h"
35 #include "ui/views/painter.h"
36
37 namespace ash {
38 namespace tray {
39
40 namespace {
41
42 const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
43 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
44 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
45 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
46 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
47 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
48 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
49 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
50 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
51 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
52 };
53
54 const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
55 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
56 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
57 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
58 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
59 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
60 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
61 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
62 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
63 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
64 };
65
66 // When a hover border is used, it is starting this many pixels before the icon
67 // position.
68 const int kTrayUserTileHoverBorderInset = 10;
69
70 // Offsetting the popup message relative to the tray menu.
71 const int kPopupMessageOffset = 25;
72
73 // Switch to a user with the given |user_index|.
74 void SwitchUser(UserIndex user_index) {
75 // Do not switch users when the log screen is presented.
76 SessionStateDelegate* delegate = WmShell::Get()->GetSessionStateDelegate();
77 if (delegate->IsUserSessionBlocked())
78 return;
79
80 DCHECK(user_index > 0);
81 MultiProfileUMA::RecordSwitchActiveUser(
82 MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
83 delegate->SwitchActiveUser(delegate->GetUserInfo(user_index)->GetAccountId());
84 }
85
86 bool IsMultiProfileSupportedAndUserActive() {
87 return WmShell::Get()->delegate()->IsMultiProfilesEnabled() &&
88 !WmShell::Get()->GetSessionStateDelegate()->IsUserSessionBlocked();
89 }
90
91 class UserViewMouseWatcherHost : public views::MouseWatcherHost {
92 public:
93 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
94 : screen_area_(screen_area) {}
95 ~UserViewMouseWatcherHost() override {}
96
97 // Implementation of MouseWatcherHost.
98 bool Contains(const gfx::Point& screen_point,
99 views::MouseWatcherHost::MouseEventType type) override {
100 return screen_area_.Contains(screen_point);
101 }
102
103 private:
104 gfx::Rect screen_area_;
105
106 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
107 };
108
109 // The menu item view which gets shown when the user clicks in multi profile
110 // mode onto the user item.
111 class AddUserView : public views::View {
112 public:
113 // The |owner| is the view for which this view gets created.
114 AddUserView(ButtonFromView* owner);
115 ~AddUserView() override;
116
117 // Get the anchor view for a message.
118 views::View* anchor() { return anchor_; }
119
120 private:
121 // Overridden from views::View.
122 gfx::Size GetPreferredSize() const override;
123
124 // Create the additional client content for this item.
125 void AddContent();
126
127 // This is the content we create and show.
128 views::View* add_user_;
129
130 // This is the owner view of this item.
131 ButtonFromView* owner_;
132
133 // The anchor view for targetted bubble messages.
134 views::View* anchor_;
135
136 DISALLOW_COPY_AND_ASSIGN(AddUserView);
137 };
138
139 AddUserView::AddUserView(ButtonFromView* owner)
140 : add_user_(NULL), owner_(owner), anchor_(NULL) {
141 AddContent();
142 owner_->ForceBorderVisible(true);
143 }
144
145 AddUserView::~AddUserView() {
146 owner_->ForceBorderVisible(false);
147 }
148
149 gfx::Size AddUserView::GetPreferredSize() const {
150 return owner_->bounds().size();
151 }
152
153 void AddUserView::AddContent() {
154 SetLayoutManager(new views::FillLayout());
155 set_background(views::Background::CreateSolidBackground(kBackgroundColor));
156
157 add_user_ = new views::View;
158 add_user_->SetBorder(
159 views::Border::CreateEmptyBorder(0, kTrayUserTileHoverBorderInset, 0, 0));
160
161 add_user_->SetLayoutManager(new views::BoxLayout(
162 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
163 AddChildViewAt(add_user_, 0);
164
165 // Add the [+] icon which is also the anchor for messages.
166 RoundedImageView* icon = new RoundedImageView(kTrayRoundedBorderRadius, true);
167 anchor_ = icon;
168 icon->SetImage(*ui::ResourceBundle::GetSharedInstance()
169 .GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER)
170 .ToImageSkia(),
171 gfx::Size(kTrayItemSize, kTrayItemSize));
172 add_user_->AddChildView(icon);
173
174 // Add the command text.
175 views::Label* command_label = new views::Label(
176 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
177 command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
178 add_user_->AddChildView(command_label);
179 }
180
181 } // namespace
182
183 UserView::UserView(SystemTrayItem* owner, LoginStatus login, UserIndex index)
184 : user_index_(index),
185 user_card_view_(NULL),
186 owner_(owner),
187 is_user_card_button_(false),
188 logout_button_(NULL),
189 add_user_enabled_(true),
190 focus_manager_(NULL) {
191 CHECK_NE(LoginStatus::NOT_LOGGED_IN, login);
192 if (!index) {
193 // Only the logged in user will have a background. All other users will have
194 // to allow the TrayPopupContainer highlighting the menu line.
195 set_background(views::Background::CreateSolidBackground(
196 login == LoginStatus::PUBLIC ? kPublicAccountBackgroundColor
197 : kBackgroundColor));
198 }
199 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
200 kTrayPopupPaddingBetweenItems));
201 // The logout button must be added before the user card so that the user card
202 // can correctly calculate the remaining available width.
203 // Note that only the current multiprofile user gets a button.
204 if (!user_index_)
205 AddLogoutButton(login);
206 AddUserCard(login);
207 }
208
209 UserView::~UserView() {
210 RemoveAddUserMenuOption();
211 }
212
213 void UserView::MouseMovedOutOfHost() {
214 RemoveAddUserMenuOption();
215 }
216
217 TrayUser::TestState UserView::GetStateForTest() const {
218 if (add_menu_option_.get()) {
219 return add_user_enabled_ ? TrayUser::ACTIVE : TrayUser::ACTIVE_BUT_DISABLED;
220 }
221
222 if (!is_user_card_button_)
223 return TrayUser::SHOWN;
224
225 return static_cast<ButtonFromView*>(user_card_view_)->is_hovered_for_test()
226 ? TrayUser::HOVERED
227 : TrayUser::SHOWN;
228 }
229
230 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
231 DCHECK(user_card_view_);
232 return user_card_view_->GetBoundsInScreen();
233 }
234
235 gfx::Size UserView::GetPreferredSize() const {
236 gfx::Size size = views::View::GetPreferredSize();
237 // Only the active user panel will be forced to a certain height.
238 if (!user_index_) {
239 size.set_height(
240 std::max(size.height(), kTrayPopupItemHeight + GetInsets().height()));
241 }
242 return size;
243 }
244
245 int UserView::GetHeightForWidth(int width) const {
246 return GetPreferredSize().height();
247 }
248
249 void UserView::Layout() {
250 gfx::Rect contents_area(GetContentsBounds());
251 if (user_card_view_ && logout_button_) {
252 // Give the logout button the space it requests.
253 gfx::Rect logout_area = contents_area;
254 logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
255 logout_area.set_x(contents_area.right() - logout_area.width());
256
257 // Give the remaining space to the user card.
258 gfx::Rect user_card_area = contents_area;
259 int remaining_width = contents_area.width() - logout_area.width();
260 if (IsMultiProfileSupportedAndUserActive()) {
261 // In multiprofile case |user_card_view_| and |logout_button_| have to
262 // have the same height.
263 int y = std::min(user_card_area.y(), logout_area.y());
264 int height = std::max(user_card_area.height(), logout_area.height());
265 logout_area.set_y(y);
266 logout_area.set_height(height);
267 user_card_area.set_y(y);
268 user_card_area.set_height(height);
269
270 // In multiprofile mode we have also to increase the size of the card by
271 // the size of the border to make it overlap with the logout button.
272 user_card_area.set_width(std::max(0, remaining_width + 1));
273
274 // To make the logout button symmetrical with the user card we also make
275 // the button longer by the same size the hover area in front of the icon
276 // got inset.
277 logout_area.set_width(logout_area.width() +
278 kTrayUserTileHoverBorderInset);
279 } else {
280 // In all other modes we have to make sure that there is enough spacing
281 // between the two.
282 remaining_width -= kTrayPopupPaddingBetweenItems;
283 }
284 user_card_area.set_width(remaining_width);
285 user_card_view_->SetBoundsRect(user_card_area);
286 logout_button_->SetBoundsRect(logout_area);
287 } else if (user_card_view_) {
288 user_card_view_->SetBoundsRect(contents_area);
289 } else if (logout_button_) {
290 logout_button_->SetBoundsRect(contents_area);
291 }
292 }
293
294 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
295 if (sender == logout_button_) {
296 WmShell::Get()->RecordUserMetricsAction(UMA_STATUS_AREA_SIGN_OUT);
297 RemoveAddUserMenuOption();
298 WmShell::Get()->system_tray_delegate()->SignOut();
299 } else if (sender == user_card_view_ &&
300 IsMultiProfileSupportedAndUserActive()) {
301 if (!user_index_) {
302 ToggleAddUserMenuOption();
303 } else {
304 RemoveAddUserMenuOption();
305 SwitchUser(user_index_);
306 // Since the user list is about to change the system menu should get
307 // closed.
308 owner_->system_tray()->CloseSystemBubble();
309 }
310 } else if (add_menu_option_.get() &&
311 sender == add_menu_option_->GetContentsView()) {
312 RemoveAddUserMenuOption();
313 // Let the user add another account to the session.
314 MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
315 WmShell::Get()->system_tray_delegate()->ShowUserLogin();
316 owner_->system_tray()->CloseSystemBubble();
317 } else {
318 NOTREACHED();
319 }
320 }
321
322 void UserView::OnWillChangeFocus(View* focused_before, View* focused_now) {
323 if (focused_now)
324 RemoveAddUserMenuOption();
325 }
326
327 void UserView::OnDidChangeFocus(View* focused_before, View* focused_now) {
328 // Nothing to do here.
329 }
330
331 void UserView::AddLogoutButton(LoginStatus login) {
332 const base::string16 title =
333 user::GetLocalizedSignOutStringForStatus(login, true);
334 auto* logout_button = new TrayPopupLabelButton(this, title);
335 logout_button->SetAccessibleName(title);
336 logout_button_ = logout_button;
337 // In public account mode, the logout button border has a custom color.
338 if (login == LoginStatus::PUBLIC) {
339 std::unique_ptr<TrayPopupLabelButtonBorder> border(
340 new TrayPopupLabelButtonBorder());
341 border->SetPainter(false, views::Button::STATE_NORMAL,
342 views::Painter::CreateImageGridPainter(
343 kPublicAccountLogoutButtonBorderImagesNormal));
344 border->SetPainter(false, views::Button::STATE_HOVERED,
345 views::Painter::CreateImageGridPainter(
346 kPublicAccountLogoutButtonBorderImagesHovered));
347 border->SetPainter(false, views::Button::STATE_PRESSED,
348 views::Painter::CreateImageGridPainter(
349 kPublicAccountLogoutButtonBorderImagesHovered));
350 logout_button_->SetBorder(std::move(border));
351 }
352 AddChildView(logout_button_);
353 }
354
355 void UserView::AddUserCard(LoginStatus login) {
356 // Add padding around the panel.
357 SetBorder(views::Border::CreateEmptyBorder(
358 kTrayPopupUserCardVerticalPadding, kTrayPopupPaddingHorizontal,
359 kTrayPopupUserCardVerticalPadding, kTrayPopupPaddingHorizontal));
360
361 views::TrayBubbleView* bubble_view =
362 owner_->system_tray()->GetSystemBubble()->bubble_view();
363 int max_card_width =
364 bubble_view->GetMaximumSize().width() -
365 (2 * kTrayPopupPaddingHorizontal + kTrayPopupPaddingBetweenItems);
366 if (logout_button_)
367 max_card_width -= logout_button_->GetPreferredSize().width();
368 user_card_view_ = new UserCardView(login, max_card_width, user_index_);
369 // The entry is clickable when no system modal dialog is open and the multi
370 // profile option is active.
371 bool clickable = !WmShell::Get()->IsSystemModalWindowOpen() &&
372 IsMultiProfileSupportedAndUserActive();
373 if (clickable) {
374 // To allow the border to start before the icon, reduce the size before and
375 // add an inset to the icon to get the spacing.
376 if (!user_index_) {
377 SetBorder(views::Border::CreateEmptyBorder(
378 kTrayPopupUserCardVerticalPadding,
379 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
380 kTrayPopupUserCardVerticalPadding, kTrayPopupPaddingHorizontal));
381 user_card_view_->SetBorder(views::Border::CreateEmptyBorder(
382 0, kTrayUserTileHoverBorderInset, 0, 0));
383 }
384 gfx::Insets insets = gfx::Insets(1, 1, 1, 1);
385 views::View* contents_view = user_card_view_;
386 if (user_index_) {
387 // Since the activation border needs to be drawn around the tile, we
388 // have to put the tile into another view which fills the menu panel,
389 // but keeping the offsets of the content.
390 contents_view = new views::View();
391 contents_view->SetBorder(views::Border::CreateEmptyBorder(
392 kTrayPopupUserCardVerticalPadding, kTrayPopupPaddingHorizontal,
393 kTrayPopupUserCardVerticalPadding, kTrayPopupPaddingHorizontal));
394 contents_view->SetLayoutManager(new views::FillLayout());
395 SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 0));
396 contents_view->AddChildView(user_card_view_);
397 insets = gfx::Insets(1, 1, 1, 3);
398 }
399 auto* button =
400 new ButtonFromView(contents_view, this, !user_index_, insets);
401 user_card_view_ = button;
402 is_user_card_button_ = true;
403 }
404 AddChildViewAt(user_card_view_, 0);
405 // Card for supervised user can consume more space than currently
406 // available. In that case we should increase system bubble's width.
407 if (login == LoginStatus::PUBLIC)
408 bubble_view->SetWidth(GetPreferredSize().width());
409 }
410
411 void UserView::ToggleAddUserMenuOption() {
412 if (add_menu_option_.get()) {
413 RemoveAddUserMenuOption();
414 return;
415 }
416
417 // Note: We do not need to install a global event handler to delete this
418 // item since it will destroyed automatically before the menu / user menu item
419 // gets destroyed..
420 add_menu_option_.reset(new views::Widget);
421 views::Widget::InitParams params;
422 params.type = views::Widget::InitParams::TYPE_TOOLTIP;
423 params.keep_on_top = true;
424 params.accept_events = true;
425 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
426 params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
427 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
428 params.name = "AddUserMenuOption";
429 WmLookup::Get()
430 ->GetWindowForWidget(GetWidget())
431 ->GetRootWindowController()
432 ->ConfigureWidgetInitParamsForContainer(
433 add_menu_option_.get(), kShellWindowId_DragImageAndTooltipContainer,
434 &params);
435 add_menu_option_->Init(params);
436 add_menu_option_->SetOpacity(1.f);
437
438 // Position it below our user card.
439 gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
440 bounds.set_y(bounds.y() + bounds.height());
441 add_menu_option_->SetBounds(bounds);
442
443 // Show the content.
444 add_menu_option_->SetAlwaysOnTop(true);
445 add_menu_option_->Show();
446
447 AddUserView* add_user_view =
448 new AddUserView(static_cast<ButtonFromView*>(user_card_view_));
449
450 const SessionStateDelegate* delegate =
451 WmShell::Get()->GetSessionStateDelegate();
452
453 SessionStateDelegate::AddUserError add_user_error;
454 add_user_enabled_ = delegate->CanAddUserToMultiProfile(&add_user_error);
455
456 ButtonFromView* button =
457 new ButtonFromView(add_user_view, add_user_enabled_ ? this : NULL,
458 add_user_enabled_, gfx::Insets(1, 1, 1, 1));
459 button->SetAccessibleName(
460 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
461 button->ForceBorderVisible(true);
462 add_menu_option_->SetContentsView(button);
463
464 if (add_user_enabled_) {
465 // We activate the entry automatically if invoked with focus.
466 if (user_card_view_->HasFocus()) {
467 button->GetFocusManager()->SetFocusedView(button);
468 user_card_view_->GetFocusManager()->SetFocusedView(button);
469 }
470 } else {
471 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
472 int message_id = 0;
473 switch (add_user_error) {
474 case SessionStateDelegate::ADD_USER_ERROR_NOT_ALLOWED_PRIMARY_USER:
475 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_NOT_ALLOWED_PRIMARY_USER;
476 break;
477 case SessionStateDelegate::ADD_USER_ERROR_MAXIMUM_USERS_REACHED:
478 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER;
479 break;
480 case SessionStateDelegate::ADD_USER_ERROR_OUT_OF_USERS:
481 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_OUT_OF_USERS;
482 break;
483 default:
484 NOTREACHED() << "Unknown adding user error " << add_user_error;
485 }
486
487 popup_message_.reset(new PopupMessage(
488 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
489 bundle.GetLocalizedString(message_id), PopupMessage::ICON_WARNING,
490 add_user_view->anchor(), views::BubbleBorder::TOP_LEFT,
491 gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
492 2 * kPopupMessageOffset));
493 }
494 // Find the screen area which encloses both elements and sets then a mouse
495 // watcher which will close the "menu".
496 gfx::Rect area = user_card_view_->GetBoundsInScreen();
497 area.set_height(2 * area.height());
498 mouse_watcher_.reset(
499 new views::MouseWatcher(new UserViewMouseWatcherHost(area), this));
500 mouse_watcher_->Start();
501 // Install a listener to focus changes so that we can remove the card when
502 // the focus gets changed. When called through the destruction of the bubble,
503 // the FocusManager cannot be determined anymore and we remember it here.
504 focus_manager_ = user_card_view_->GetFocusManager();
505 focus_manager_->AddFocusChangeListener(this);
506 }
507
508 void UserView::RemoveAddUserMenuOption() {
509 if (!add_menu_option_.get())
510 return;
511 focus_manager_->RemoveFocusChangeListener(this);
512 focus_manager_ = NULL;
513 if (user_card_view_->GetFocusManager())
514 user_card_view_->GetFocusManager()->ClearFocus();
515 popup_message_.reset();
516 mouse_watcher_.reset();
517 add_menu_option_.reset();
518 }
519
520 } // namespace tray
521 } // namespace ash
OLDNEW
« ash/common/ash_view_ids.h ('K') | « ash/system/user/user_view.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698