OLD | NEW |
| (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/common/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/session/session_state_delegate.h" | |
12 #include "ash/common/shell_delegate.h" | |
13 #include "ash/common/system/tray/system_tray.h" | |
14 #include "ash/common/system/tray/system_tray_controller.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_item_style.h" | |
18 #include "ash/common/system/tray/tray_popup_utils.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/system/user/user_card_view.h" | |
23 #include "ash/common/wm_shell.h" | |
24 #include "ash/common/wm_window.h" | |
25 #include "ash/public/cpp/shell_window_ids.h" | |
26 #include "ash/resources/grit/ash_resources.h" | |
27 #include "ash/resources/vector_icons/vector_icons.h" | |
28 #include "ash/root_window_controller.h" | |
29 #include "ash/strings/grit/ash_strings.h" | |
30 #include "base/memory/ptr_util.h" | |
31 #include "components/signin/core/account_id/account_id.h" | |
32 #include "components/user_manager/user_info.h" | |
33 #include "ui/base/l10n/l10n_util.h" | |
34 #include "ui/base/resource/resource_bundle.h" | |
35 #include "ui/gfx/canvas.h" | |
36 #include "ui/gfx/geometry/insets.h" | |
37 #include "ui/gfx/paint_vector_icon.h" | |
38 #include "ui/views/controls/button/label_button.h" | |
39 #include "ui/views/controls/label.h" | |
40 #include "ui/views/controls/separator.h" | |
41 #include "ui/views/layout/fill_layout.h" | |
42 #include "ui/views/painter.h" | |
43 | |
44 namespace ash { | |
45 namespace tray { | |
46 | |
47 namespace { | |
48 | |
49 // Switch to a user with the given |user_index|. | |
50 void SwitchUser(UserIndex user_index) { | |
51 // Do not switch users when the log screen is presented. | |
52 SessionStateDelegate* delegate = WmShell::Get()->GetSessionStateDelegate(); | |
53 if (delegate->IsUserSessionBlocked()) | |
54 return; | |
55 | |
56 DCHECK(user_index > 0); | |
57 MultiProfileUMA::RecordSwitchActiveUser( | |
58 MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY); | |
59 delegate->SwitchActiveUser(delegate->GetUserInfo(user_index)->GetAccountId()); | |
60 } | |
61 | |
62 bool IsMultiProfileSupportedAndUserActive() { | |
63 return WmShell::Get()->delegate()->IsMultiProfilesEnabled() && | |
64 !WmShell::Get()->GetSessionStateDelegate()->IsUserSessionBlocked(); | |
65 } | |
66 | |
67 // Creates the view shown in the user switcher popup ("AddUserMenuOption"). | |
68 views::View* CreateAddUserView(AddUserSessionPolicy policy, | |
69 views::ButtonListener* listener) { | |
70 auto* view = new views::View; | |
71 const int icon_padding = (kMenuButtonSize - kMenuIconSize) / 2; | |
72 auto* layout = | |
73 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, | |
74 kTrayPopupLabelHorizontalPadding + icon_padding); | |
75 layout->set_minimum_cross_axis_size(kTrayPopupItemMinHeight); | |
76 view->SetLayoutManager(layout); | |
77 view->set_background( | |
78 views::Background::CreateSolidBackground(kBackgroundColor)); | |
79 | |
80 int message_id = 0; | |
81 switch (policy) { | |
82 case AddUserSessionPolicy::ALLOWED: { | |
83 message_id = IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT; | |
84 | |
85 auto* icon = new views::ImageView(); | |
86 icon->SetImage( | |
87 gfx::CreateVectorIcon(kSystemMenuNewUserIcon, kMenuIconColor)); | |
88 view->AddChildView(icon); | |
89 break; | |
90 } | |
91 case AddUserSessionPolicy::ERROR_NOT_ALLOWED_PRIMARY_USER: | |
92 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_NOT_ALLOWED_PRIMARY_USER; | |
93 break; | |
94 case AddUserSessionPolicy::ERROR_MAXIMUM_USERS_REACHED: | |
95 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER; | |
96 break; | |
97 case AddUserSessionPolicy::ERROR_NO_ELIGIBLE_USERS: | |
98 message_id = IDS_ASH_STATUS_TRAY_MESSAGE_OUT_OF_USERS; | |
99 break; | |
100 } | |
101 | |
102 auto* command_label = new views::Label(l10n_util::GetStringUTF16(message_id)); | |
103 command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
104 command_label->SetMultiLine(true); | |
105 | |
106 TrayPopupItemStyle label_style( | |
107 TrayPopupItemStyle::FontStyle::DETAILED_VIEW_LABEL); | |
108 int vertical_padding = kMenuSeparatorVerticalPadding; | |
109 if (policy != AddUserSessionPolicy::ALLOWED) { | |
110 label_style.set_font_style(TrayPopupItemStyle::FontStyle::CAPTION); | |
111 label_style.set_color_style(TrayPopupItemStyle::ColorStyle::INACTIVE); | |
112 vertical_padding += kMenuSeparatorVerticalPadding; | |
113 } | |
114 label_style.SetupLabel(command_label); | |
115 view->AddChildView(command_label); | |
116 view->SetBorder(views::CreateEmptyBorder(vertical_padding, icon_padding, | |
117 vertical_padding, | |
118 kTrayPopupLabelHorizontalPadding)); | |
119 if (policy == AddUserSessionPolicy::ALLOWED) { | |
120 auto* button = | |
121 new ButtonFromView(view, listener, TrayPopupInkDropStyle::INSET_BOUNDS); | |
122 button->SetAccessibleName( | |
123 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT)); | |
124 return button; | |
125 } | |
126 | |
127 return view; | |
128 } | |
129 | |
130 class UserViewMouseWatcherHost : public views::MouseWatcherHost { | |
131 public: | |
132 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area) | |
133 : screen_area_(screen_area) {} | |
134 ~UserViewMouseWatcherHost() override {} | |
135 | |
136 // Implementation of MouseWatcherHost. | |
137 bool Contains(const gfx::Point& screen_point, | |
138 views::MouseWatcherHost::MouseEventType type) override { | |
139 return screen_area_.Contains(screen_point); | |
140 } | |
141 | |
142 private: | |
143 gfx::Rect screen_area_; | |
144 | |
145 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost); | |
146 }; | |
147 | |
148 // A view that acts as the contents of the widget that appears when clicking | |
149 // the active user. If the mouse exits this view or an otherwise unhandled | |
150 // click is detected, it will invoke a closure passed at construction time. | |
151 class AddUserWidgetContents : public views::View { | |
152 public: | |
153 explicit AddUserWidgetContents(const base::Closure& close_widget) | |
154 : close_widget_(close_widget) { | |
155 // Don't want to receive a mouse exit event when the cursor enters a child. | |
156 set_notify_enter_exit_on_child(true); | |
157 } | |
158 | |
159 ~AddUserWidgetContents() override {} | |
160 | |
161 bool OnMousePressed(const ui::MouseEvent& event) override { return true; } | |
162 void OnMouseReleased(const ui::MouseEvent& event) override { | |
163 close_widget_.Run(); | |
164 } | |
165 void OnMouseExited(const ui::MouseEvent& event) override { | |
166 close_widget_.Run(); | |
167 } | |
168 void OnGestureEvent(ui::GestureEvent* event) override { close_widget_.Run(); } | |
169 | |
170 private: | |
171 base::Closure close_widget_; | |
172 | |
173 DISALLOW_COPY_AND_ASSIGN(AddUserWidgetContents); | |
174 }; | |
175 | |
176 // This border reserves 4dp above and 8dp below and paints a horizontal | |
177 // separator 3dp below the host view. | |
178 class ActiveUserBorder : public views::Border { | |
179 public: | |
180 ActiveUserBorder() {} | |
181 ~ActiveUserBorder() override {} | |
182 | |
183 // views::Border: | |
184 void Paint(const views::View& view, gfx::Canvas* canvas) override { | |
185 canvas->FillRect( | |
186 gfx::Rect( | |
187 0, view.height() - kMenuSeparatorVerticalPadding - kSeparatorWidth, | |
188 view.width(), kSeparatorWidth), | |
189 kMenuSeparatorColor); | |
190 } | |
191 | |
192 gfx::Insets GetInsets() const override { | |
193 return gfx::Insets(kMenuSeparatorVerticalPadding, | |
194 kMenuExtraMarginFromLeftEdge, | |
195 kMenuSeparatorVerticalPadding * 2, 0); | |
196 } | |
197 | |
198 gfx::Size GetMinimumSize() const override { return gfx::Size(); } | |
199 | |
200 private: | |
201 DISALLOW_COPY_AND_ASSIGN(ActiveUserBorder); | |
202 }; | |
203 | |
204 } // namespace | |
205 | |
206 UserView::UserView(SystemTrayItem* owner, LoginStatus login, UserIndex index) | |
207 : user_index_(index), | |
208 user_card_view_(nullptr), | |
209 owner_(owner), | |
210 is_user_card_button_(false), | |
211 logout_button_(nullptr), | |
212 add_user_enabled_(true), | |
213 focus_manager_(nullptr) { | |
214 CHECK_NE(LoginStatus::NOT_LOGGED_IN, login); | |
215 // The logout button must be added before the user card so that the user card | |
216 // can correctly calculate the remaining available width. | |
217 // Note that only the current multiprofile user gets a button. | |
218 if (IsActiveUser()) | |
219 AddLogoutButton(login); | |
220 AddUserCard(login); | |
221 | |
222 auto* layout = new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0); | |
223 SetLayoutManager(layout); | |
224 layout->set_cross_axis_alignment( | |
225 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); | |
226 layout->SetFlexForView(user_card_view_, 1); | |
227 | |
228 if (IsActiveUser()) | |
229 SetBorder(base::MakeUnique<ActiveUserBorder>()); | |
230 } | |
231 | |
232 UserView::~UserView() { | |
233 RemoveAddUserMenuOption(); | |
234 } | |
235 | |
236 TrayUser::TestState UserView::GetStateForTest() const { | |
237 if (add_menu_option_) | |
238 return add_user_enabled_ ? TrayUser::ACTIVE : TrayUser::ACTIVE_BUT_DISABLED; | |
239 | |
240 if (!is_user_card_button_) | |
241 return TrayUser::SHOWN; | |
242 | |
243 return static_cast<ButtonFromView*>(user_card_view_)->is_hovered_for_test() | |
244 ? TrayUser::HOVERED | |
245 : TrayUser::SHOWN; | |
246 } | |
247 | |
248 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() { | |
249 DCHECK(user_card_view_); | |
250 return user_card_view_->GetBoundsInScreen(); | |
251 } | |
252 | |
253 bool UserView::IsActiveUser() const { | |
254 return user_index_ == 0; | |
255 } | |
256 | |
257 int UserView::GetHeightForWidth(int width) const { | |
258 return GetPreferredSize().height(); | |
259 } | |
260 | |
261 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) { | |
262 if (sender == logout_button_) { | |
263 WmShell::Get()->RecordUserMetricsAction(UMA_STATUS_AREA_SIGN_OUT); | |
264 RemoveAddUserMenuOption(); | |
265 WmShell::Get()->system_tray_controller()->SignOut(); | |
266 } else if (sender == user_card_view_ && | |
267 IsMultiProfileSupportedAndUserActive()) { | |
268 if (IsActiveUser()) { | |
269 ToggleAddUserMenuOption(); | |
270 } else { | |
271 RemoveAddUserMenuOption(); | |
272 SwitchUser(user_index_); | |
273 // Since the user list is about to change the system menu should get | |
274 // closed. | |
275 owner_->system_tray()->CloseSystemBubble(); | |
276 } | |
277 } else if (add_menu_option_ && | |
278 sender->GetWidget() == add_menu_option_.get()) { | |
279 RemoveAddUserMenuOption(); | |
280 // Let the user add another account to the session. | |
281 MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY); | |
282 WmShell::Get()->system_tray_delegate()->ShowUserLogin(); | |
283 owner_->system_tray()->CloseSystemBubble(); | |
284 } else { | |
285 NOTREACHED(); | |
286 } | |
287 } | |
288 | |
289 void UserView::OnWillChangeFocus(View* focused_before, View* focused_now) { | |
290 if (focused_now) | |
291 RemoveAddUserMenuOption(); | |
292 } | |
293 | |
294 void UserView::OnDidChangeFocus(View* focused_before, View* focused_now) { | |
295 // Nothing to do here. | |
296 } | |
297 | |
298 void UserView::AddLogoutButton(LoginStatus login) { | |
299 AddChildView(TrayPopupUtils::CreateVerticalSeparator()); | |
300 logout_button_ = TrayPopupUtils::CreateTrayPopupBorderlessButton( | |
301 this, user::GetLocalizedSignOutStringForStatus(login, true)); | |
302 AddChildView(logout_button_); | |
303 } | |
304 | |
305 void UserView::AddUserCard(LoginStatus login) { | |
306 user_card_view_ = new UserCardView(login, -1, user_index_); | |
307 // The entry is clickable when no system modal dialog is open and the multi | |
308 // profile option is active. | |
309 bool clickable = !WmShell::Get()->IsSystemModalWindowOpen() && | |
310 IsMultiProfileSupportedAndUserActive(); | |
311 if (clickable) { | |
312 views::View* contents_view = user_card_view_; | |
313 auto* button = | |
314 new ButtonFromView(contents_view, this, | |
315 IsActiveUser() ? TrayPopupInkDropStyle::INSET_BOUNDS | |
316 : TrayPopupInkDropStyle::FILL_BOUNDS); | |
317 user_card_view_ = button; | |
318 is_user_card_button_ = true; | |
319 } | |
320 AddChildViewAt(user_card_view_, 0); | |
321 } | |
322 | |
323 void UserView::ToggleAddUserMenuOption() { | |
324 if (add_menu_option_) { | |
325 RemoveAddUserMenuOption(); | |
326 return; | |
327 } | |
328 | |
329 // Note: We do not need to install a global event handler to delete this | |
330 // item since it will destroyed automatically before the menu / user menu item | |
331 // gets destroyed.. | |
332 add_menu_option_.reset(new views::Widget); | |
333 views::Widget::InitParams params; | |
334 params.type = views::Widget::InitParams::TYPE_TOOLTIP; | |
335 params.keep_on_top = true; | |
336 params.accept_events = true; | |
337 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
338 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; | |
339 params.name = "AddUserMenuOption"; | |
340 WmWindow::Get(GetWidget()->GetNativeWindow()) | |
341 ->GetRootWindowController() | |
342 ->ConfigureWidgetInitParamsForContainer( | |
343 add_menu_option_.get(), kShellWindowId_DragImageAndTooltipContainer, | |
344 ¶ms); | |
345 add_menu_option_->Init(params); | |
346 | |
347 const SessionStateDelegate* delegate = | |
348 WmShell::Get()->GetSessionStateDelegate(); | |
349 const AddUserSessionPolicy add_user_policy = | |
350 delegate->GetAddUserSessionPolicy(); | |
351 add_user_enabled_ = add_user_policy == AddUserSessionPolicy::ALLOWED; | |
352 | |
353 // Position the widget on top of the user card view (which is still in the | |
354 // system menu). The top half of the widget will be transparent to allow | |
355 // the active user to show through. | |
356 gfx::Rect bounds = user_card_view_->GetBoundsInScreen(); | |
357 bounds.set_width(bounds.width() + kSeparatorWidth); | |
358 int row_height = bounds.height(); | |
359 | |
360 views::View* container = new AddUserWidgetContents( | |
361 base::Bind(&UserView::RemoveAddUserMenuOption, base::Unretained(this))); | |
362 container->SetBorder(views::CreatePaddedBorder( | |
363 views::CreateSolidSidedBorder(0, 0, 0, kSeparatorWidth, kBackgroundColor), | |
364 gfx::Insets(row_height, 0, 0, 0))); | |
365 views::View* add_user_padding = new views::View(); | |
366 add_user_padding->SetBorder(views::CreateSolidSidedBorder( | |
367 kMenuSeparatorVerticalPadding, 0, 0, 0, kBackgroundColor)); | |
368 views::View* add_user_view = CreateAddUserView(add_user_policy, this); | |
369 add_user_padding->AddChildView(add_user_view); | |
370 add_user_padding->SetLayoutManager(new views::FillLayout()); | |
371 container->AddChildView(add_user_padding); | |
372 container->SetLayoutManager(new views::FillLayout()); | |
373 add_menu_option_->SetContentsView(container); | |
374 | |
375 bounds.set_height(container->GetPreferredSize().height()); | |
376 add_menu_option_->SetBounds(bounds); | |
377 | |
378 // Show the content. | |
379 add_menu_option_->SetAlwaysOnTop(true); | |
380 add_menu_option_->Show(); | |
381 | |
382 // Install a listener to focus changes so that we can remove the card when | |
383 // the focus gets changed. When called through the destruction of the bubble, | |
384 // the FocusManager cannot be determined anymore and we remember it here. | |
385 focus_manager_ = user_card_view_->GetFocusManager(); | |
386 focus_manager_->AddFocusChangeListener(this); | |
387 } | |
388 | |
389 void UserView::RemoveAddUserMenuOption() { | |
390 if (!add_menu_option_) | |
391 return; | |
392 focus_manager_->RemoveFocusChangeListener(this); | |
393 focus_manager_ = nullptr; | |
394 if (user_card_view_->GetFocusManager()) | |
395 user_card_view_->GetFocusManager()->ClearFocus(); | |
396 add_menu_option_.reset(); | |
397 } | |
398 | |
399 } // namespace tray | |
400 } // namespace ash | |
OLD | NEW |