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