| 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 |