1 // Copyright 2017 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/shelf/login_shelf_view.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <string>
10 #include <utility>
11 
12 #include "ash/focus_cycler.h"
13 #include "ash/keyboard/ui/keyboard_ui_controller.h"
14 #include "ash/lock_screen_action/lock_screen_action_background_state.h"
15 #include "ash/login/login_screen_controller.h"
16 #include "ash/login/ui/lock_screen.h"
17 #include "ash/public/cpp/ash_constants.h"
18 #include "ash/public/cpp/login_accelerators.h"
19 #include "ash/public/cpp/login_constants.h"
20 #include "ash/public/cpp/shelf_config.h"
21 #include "ash/resources/vector_icons/vector_icons.h"
22 #include "ash/root_window_controller.h"
23 #include "ash/session/session_controller_impl.h"
24 #include "ash/shelf/shelf.h"
25 #include "ash/shelf/shelf_widget.h"
26 #include "ash/shell.h"
27 #include "ash/strings/grit/ash_strings.h"
28 #include "ash/style/ash_color_provider.h"
29 #include "ash/style/default_color_constants.h"
30 #include "ash/style/default_colors.h"
31 #include "ash/system/status_area_widget.h"
32 #include "ash/system/status_area_widget_delegate.h"
33 #include "ash/system/tray/system_tray_notifier.h"
34 #include "ash/system/tray/tray_popup_utils.h"
35 #include "ash/wm/lock_state_controller.h"
36 #include "base/bind.h"
37 #include "base/callback_helpers.h"
38 #include "base/metrics/user_metrics.h"
39 #include "base/sequence_checker.h"
40 #include "base/threading/thread_task_runner_handle.h"
41 #include "chromeos/constants/chromeos_switches.h"
42 #include "chromeos/strings/grit/chromeos_strings.h"
43 #include "chromeos/ui/vector_icons/vector_icons.h"
44 #include "skia/ext/image_operations.h"
45 #include "ui/accessibility/ax_node_data.h"
46 #include "ui/base/l10n/l10n_util.h"
47 #include "ui/base/models/image_model.h"
48 #include "ui/base/models/menu_model.h"
49 #include "ui/base/models/simple_menu_model.h"
50 #include "ui/compositor/scoped_layer_animation_settings.h"
51 #include "ui/events/types/event_type.h"
52 #include "ui/gfx/canvas.h"
53 #include "ui/gfx/color_palette.h"
54 #include "ui/gfx/geometry/rect.h"
55 #include "ui/gfx/geometry/size.h"
56 #include "ui/gfx/image/image_skia_operations.h"
57 #include "ui/gfx/paint_vector_icon.h"
58 #include "ui/gfx/vector_icon_types.h"
59 #include "ui/views/accessibility/view_accessibility.h"
60 #include "ui/views/animation/ink_drop_impl.h"
61 #include "ui/views/animation/ink_drop_mask.h"
62 #include "ui/views/controls/button/button.h"
63 #include "ui/views/controls/button/label_button.h"
64 #include "ui/views/controls/button/menu_button.h"
65 #include "ui/views/controls/highlight_path_generator.h"
66 #include "ui/views/controls/menu/menu_runner.h"
67 #include "ui/views/controls/menu/menu_types.h"
68 #include "ui/views/focus/focus_search.h"
69 #include "ui/views/layout/box_layout.h"
70 #include "ui/views/view_class_properties.h"
71 #include "ui/views/widget/widget.h"
72 
73 using session_manager::SessionState;
74 
75 namespace ash {
76 namespace {
77 
78 const char* kLoginShelfButtonClassName = "LoginShelfButton";
79 
GetButtonTextColor()80 SkColor GetButtonTextColor() {
81   return DeprecatedGetContentLayerColor(
82       AshColorProvider::ContentLayerType::kButtonLabelColor,
83       kLoginShelfButtonLabelColor);
84 }
85 
GetButtonIconColor()86 SkColor GetButtonIconColor() {
87   return DeprecatedGetContentLayerColor(
88       AshColorProvider::ContentLayerType::kButtonIconColor,
89       kLoginShelfButtonIconColor);
90 }
91 
GetButtonBackgroundColor()92 SkColor GetButtonBackgroundColor() {
93   if (Shell::Get()->session_controller()->GetSessionState() ==
94       session_manager::SessionState::OOBE) {
95     return SkColorSetA(SK_ColorBLACK, 16);  // 6% opacity
96   }
97   return AshColorProvider::Get()->GetControlsLayerColor(
98       AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive);
99 }
100 
GetUserClickTarget(int button_id)101 LoginMetricsRecorder::ShelfButtonClickTarget GetUserClickTarget(int button_id) {
102   switch (button_id) {
103     case LoginShelfView::kShutdown:
104       return LoginMetricsRecorder::ShelfButtonClickTarget::kShutDownButton;
105     case LoginShelfView::kRestart:
106       return LoginMetricsRecorder::ShelfButtonClickTarget::kRestartButton;
107     case LoginShelfView::kSignOut:
108       return LoginMetricsRecorder::ShelfButtonClickTarget::kSignOutButton;
109     case LoginShelfView::kCloseNote:
110       return LoginMetricsRecorder::ShelfButtonClickTarget::kCloseNoteButton;
111     case LoginShelfView::kBrowseAsGuest:
112       return LoginMetricsRecorder::ShelfButtonClickTarget::kBrowseAsGuestButton;
113     case LoginShelfView::kAddUser:
114       return LoginMetricsRecorder::ShelfButtonClickTarget::kAddUserButton;
115     case LoginShelfView::kCancel:
116       return LoginMetricsRecorder::ShelfButtonClickTarget::kCancelButton;
117     case LoginShelfView::kParentAccess:
118       return LoginMetricsRecorder::ShelfButtonClickTarget::kParentAccessButton;
119     case LoginShelfView::kEnterpriseEnrollment:
120       return LoginMetricsRecorder::ShelfButtonClickTarget::
121           kEnterpriseEnrollmentButton;
122   }
123   return LoginMetricsRecorder::ShelfButtonClickTarget::kTargetCount;
124 }
125 
ButtonPressed(int id,base::RepeatingClosure callback)126 void ButtonPressed(int id, base::RepeatingClosure callback) {
127   UserMetricsRecorder::RecordUserClickOnShelfButton(GetUserClickTarget(id));
128   std::move(callback).Run();
129 }
130 
131 // The margins of the button contents.
132 constexpr int kButtonMarginTopDp = 18;
133 constexpr int kButtonMarginLeftDp = 18;
134 constexpr int kButtonMarginBottomDp = 18;
135 constexpr int kButtonMarginRightDp = 16;
136 
137 // Spacing between the button image and label.
138 constexpr int kImageLabelSpacingDp = 10;
139 
140 // The color of the button text during OOBE.
141 constexpr SkColor kButtonTextColorOobe = gfx::kGoogleGrey700;
142 
AnimateButtonOpacity(ui::Layer * layer,float target_opacity,base::TimeDelta animation_duration,gfx::Tween::Type type)143 void AnimateButtonOpacity(ui::Layer* layer,
144                           float target_opacity,
145                           base::TimeDelta animation_duration,
146                           gfx::Tween::Type type) {
147   ui::ScopedLayerAnimationSettings animation_setter(layer->GetAnimator());
148   animation_setter.SetTransitionDuration(animation_duration);
149   animation_setter.SetTweenType(type);
150   animation_setter.SetPreemptionStrategy(
151       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
152   layer->SetOpacity(target_opacity);
153 }
154 
155 // TODO(crbug.com/1051293): The highlight or ink drop will be incorrect if the
156 // values returned by |ShelfConfig| change midway.
GetButtonInsets()157 gfx::Insets GetButtonInsets() {
158   const int height_inset =
159       (ShelfConfig::Get()->shelf_size() - ShelfConfig::Get()->control_size()) /
160       2;
161   return gfx::Insets(height_inset, ShelfConfig::Get()->button_spacing(),
162                      height_inset, 0);
163 }
164 
GetButtonHighlightPath(const views::View * view)165 SkPath GetButtonHighlightPath(const views::View* view) {
166   gfx::Rect rect(view->GetLocalBounds());
167   rect.Inset(GetButtonInsets());
168 
169   const int border_radius = ShelfConfig::Get()->control_border_radius();
170   return SkPath().addRoundRect(gfx::RectToSkRect(rect), border_radius,
171                                border_radius);
172 }
173 
174 class LoginShelfButton : public views::LabelButton {
175  public:
LoginShelfButton(PressedCallback callback,int text_resource_id,const gfx::VectorIcon & icon)176   LoginShelfButton(PressedCallback callback,
177                    int text_resource_id,
178                    const gfx::VectorIcon& icon)
179       : LabelButton(std::move(callback),
180                     l10n_util::GetStringUTF16(text_resource_id)),
181         text_resource_id_(text_resource_id),
182         icon_(icon) {
183     SetAccessibleName(GetText());
184     AshColorProvider::Get()->DecoratePillButton(this, &icon);
185 
186     SetFocusBehavior(FocusBehavior::ALWAYS);
187     SetInstallFocusRingOnFocus(true);
188     views::InstallRoundRectHighlightPathGenerator(
189         this, GetButtonInsets(), ShelfConfig::Get()->control_border_radius());
190     focus_ring()->SetColor(ShelfConfig::Get()->shelf_focus_border_color());
191     SetFocusPainter(nullptr);
192     SetInkDropMode(InkDropMode::ON);
193     SetHasInkDropActionOnClick(true);
194     SetInkDropBaseColor(
195         DeprecatedGetInkDropBaseColor(kDefaultShelfInkDropColor));
196     SetInkDropVisibleOpacity(
197         DeprecatedGetInkDropOpacity(kDefaultShelfInkDropOpacity));
198 
199     // Layer rendering is required when the shelf background is visible, which
200     // happens when the wallpaper is not blurred.
201     SetPaintToLayer();
202     layer()->SetFillsBoundsOpaquely(false);
203 
204     SetTextSubpixelRenderingEnabled(false);
205 
206     SetImageLabelSpacing(kImageLabelSpacingDp);
207 
208     label()->SetFontList(views::Label::GetDefaultFontList().Derive(
209         1, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
210   }
211 
212   ~LoginShelfButton() override = default;
213 
text_resource_id() const214   int text_resource_id() const { return text_resource_id_; }
215 
216   // views::LabelButton:
GetInsets() const217   gfx::Insets GetInsets() const override {
218     return gfx::Insets(0, kButtonMarginLeftDp, 0, kButtonMarginRightDp);
219   }
220 
GetClassName() const221   const char* GetClassName() const override {
222     return kLoginShelfButtonClassName;
223   }
224 
PaintButtonContents(gfx::Canvas * canvas)225   void PaintButtonContents(gfx::Canvas* canvas) override {
226     cc::PaintFlags flags;
227     flags.setAntiAlias(true);
228     flags.setColor(GetButtonBackgroundColor());
229     flags.setStyle(cc::PaintFlags::kFill_Style);
230     canvas->DrawPath(GetButtonHighlightPath(this), flags);
231   }
232 
CreateInkDrop()233   std::unique_ptr<views::InkDrop> CreateInkDrop() override {
234     auto ink_drop = std::make_unique<views::InkDropImpl>(this, size());
235     ink_drop->SetShowHighlightOnHover(false);
236     ink_drop->SetShowHighlightOnFocus(false);
237     return ink_drop;
238   }
239 
GetTooltipText(const gfx::Point & p) const240   base::string16 GetTooltipText(const gfx::Point& p) const override {
241     if (label()->IsDisplayTextTruncated())
242       return label()->GetText();
243     return base::string16();
244   }
245 
PaintDarkColors()246   void PaintDarkColors() {
247     SetEnabledTextColors(kButtonTextColorOobe);
248     SetImage(views::Button::STATE_NORMAL,
249              gfx::CreateVectorIcon(icon_, kButtonTextColorOobe));
250     SchedulePaint();
251   }
252 
PaintLightColors()253   void PaintLightColors() {
254     SkColor button_text_color = GetButtonTextColor();
255     SetEnabledTextColors(button_text_color);
256     SetImage(views::Button::STATE_NORMAL,
257              gfx::CreateVectorIcon(icon_, button_text_color));
258     SchedulePaint();
259   }
260 
OnFocus()261   void OnFocus() override {
262     auto* const keyboard_controller = keyboard::KeyboardUIController::Get();
263     keyboard_controller->set_keyboard_locked(false /*lock*/);
264     keyboard_controller->HideKeyboardImplicitlyByUser();
265   }
266 
267  private:
268   const int text_resource_id_;
269   const gfx::VectorIcon& icon_;
270 
271   DISALLOW_COPY_AND_ASSIGN(LoginShelfButton);
272 };
273 
ShutdownButtonHidden(OobeDialogState state)274 bool ShutdownButtonHidden(OobeDialogState state) {
275   return state == OobeDialogState::MIGRATION ||
276          state == OobeDialogState::ENROLLMENT ||
277          state == OobeDialogState::ONBOARDING ||
278          state == OobeDialogState::KIOSK_LAUNCH ||
279          state == OobeDialogState::PASSWORD_CHANGED;
280 }
281 
282 }  // namespace
283 
284 class KioskAppsButton : public views::MenuButton,
285                         public ui::SimpleMenuModel,
286                         public ui::SimpleMenuModel::Delegate {
287  public:
KioskAppsButton()288   KioskAppsButton()
289       : MenuButton(base::BindRepeating(
290                        [](KioskAppsButton* button) {
291                          if (button->is_launch_enabled_)
292                            button->DisplayMenu();
293                        },
294                        this),
295                    l10n_util::GetStringUTF16(IDS_ASH_SHELF_APPS_BUTTON)),
296         ui::SimpleMenuModel(this) {
297     SetFocusBehavior(FocusBehavior::ALWAYS);
298     SetInstallFocusRingOnFocus(true);
299     views::InstallRoundRectHighlightPathGenerator(
300         this, GetButtonInsets(), ShelfConfig::Get()->control_border_radius());
301     focus_ring()->SetColor(ShelfConfig::Get()->shelf_focus_border_color());
302     SetFocusPainter(nullptr);
303     SetInkDropMode(InkDropMode::ON);
304     SetHasInkDropActionOnClick(true);
305     SetInkDropBaseColor(
306         DeprecatedGetInkDropBaseColor(kDefaultShelfInkDropColor));
307     SetInkDropVisibleOpacity(
308         DeprecatedGetInkDropOpacity(kDefaultShelfInkDropOpacity));
309 
310     // Layer rendering is required when the shelf background is visible, which
311     // happens when the wallpaper is not blurred.
312     SetPaintToLayer();
313     layer()->SetFillsBoundsOpaquely(false);
314 
315     SetTextSubpixelRenderingEnabled(false);
316 
317     SetImage(views::Button::STATE_NORMAL,
318              CreateVectorIcon(kShelfAppsButtonIcon, GetButtonIconColor()));
319     SetImageLabelSpacing(kImageLabelSpacingDp);
320     SetEnabledTextColors(GetButtonTextColor());
321     label()->SetFontList(views::Label::GetDefaultFontList().Derive(
322         1, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
323   }
324 
LaunchAppForTesting(const std::string & app_id)325   bool LaunchAppForTesting(const std::string& app_id) {
326     for (size_t i = 0; i < kiosk_apps_.size(); ++i) {
327       if (kiosk_apps_[i].app_id == app_id) {
328         ExecuteCommand(i, 0);
329         return true;
330       }
331     }
332     return false;
333   }
334 
335   // Replace the existing items list with a new list of kiosk app menu items.
SetApps(const std::vector<KioskAppMenuEntry> & kiosk_apps,const base::RepeatingCallback<void (const KioskAppMenuEntry &)> & launch_app,const base::RepeatingClosure & on_show_menu)336   void SetApps(
337       const std::vector<KioskAppMenuEntry>& kiosk_apps,
338       const base::RepeatingCallback<void(const KioskAppMenuEntry&)>& launch_app,
339       const base::RepeatingClosure& on_show_menu) {
340     launch_app_callback_ = launch_app;
341     on_show_menu_ = on_show_menu;
342     kiosk_apps_ = kiosk_apps;
343     Clear();
344     const gfx::Size kAppIconSize(16, 16);
345     for (size_t i = 0; i < kiosk_apps_.size(); ++i) {
346       gfx::ImageSkia icon = gfx::ImageSkiaOperations::CreateResizedImage(
347           kiosk_apps_[i].icon, skia::ImageOperations::RESIZE_GOOD,
348           kAppIconSize);
349       AddItemWithIcon(i, kiosk_apps_[i].name,
350                       ui::ImageModel::FromImageSkia(icon));
351     }
352 
353     // If the menu is being shown, update it.
354     if (menu_runner_ && menu_runner_->IsRunning()) {
355       DisplayMenu();
356     }
357   }
358 
HasApps() const359   bool HasApps() const { return !kiosk_apps_.empty(); }
360 
361   // views::MenuButton:
GetInsets() const362   gfx::Insets GetInsets() const override {
363     return gfx::Insets(kButtonMarginTopDp, kButtonMarginLeftDp,
364                        kButtonMarginBottomDp, kButtonMarginRightDp);
365   }
366 
PaintButtonContents(gfx::Canvas * canvas)367   void PaintButtonContents(gfx::Canvas* canvas) override {
368     cc::PaintFlags flags;
369     flags.setAntiAlias(true);
370     flags.setColor(ShelfConfig::Get()->GetShelfControlButtonColor());
371     flags.setStyle(cc::PaintFlags::kFill_Style);
372     canvas->DrawPath(GetButtonHighlightPath(this), flags);
373   }
374 
SetVisible(bool visible)375   void SetVisible(bool visible) override {
376     MenuButton::SetVisible(visible);
377     if (visible)
378       is_launch_enabled_ = true;
379   }
380 
CreateInkDrop()381   std::unique_ptr<views::InkDrop> CreateInkDrop() override {
382     auto ink_drop = std::make_unique<views::InkDropImpl>(this, size());
383     ink_drop->SetShowHighlightOnHover(false);
384     ink_drop->SetShowHighlightOnFocus(false);
385     return ink_drop;
386   }
387 
PaintDarkColors()388   void PaintDarkColors() {
389     SetEnabledTextColors(kButtonTextColorOobe);
390     SetImage(views::Button::STATE_NORMAL,
391              CreateVectorIcon(kShelfAppsButtonIcon, kButtonTextColorOobe));
392     SchedulePaint();
393   }
394 
PaintLightColors()395   void PaintLightColors() {
396     SetEnabledTextColors(GetButtonTextColor());
397     SetImage(views::Button::STATE_NORMAL,
398              CreateVectorIcon(kShelfAppsButtonIcon, GetButtonIconColor()));
399     SchedulePaint();
400   }
401 
DisplayMenu()402   void DisplayMenu() {
403     const gfx::Point point = GetMenuPosition();
404     const gfx::Point origin(point.x() - width(), point.y() - height());
405     menu_runner_.reset(
406         new views::MenuRunner(this, views::MenuRunner::HAS_MNEMONICS));
407     menu_runner_->RunMenuAt(GetWidget()->GetTopLevelWidget(),
408                             button_controller(), gfx::Rect(origin, gfx::Size()),
409                             views::MenuAnchorPosition::kTopLeft,
410                             ui::MENU_SOURCE_NONE);
411   }
412 
413   // ui::SimpleMenuModel:
ExecuteCommand(int command_id,int event_flags)414   void ExecuteCommand(int command_id, int event_flags) override {
415     DCHECK(command_id >= 0 &&
416            base::checked_cast<size_t>(command_id) < kiosk_apps_.size());
417     // Once an app is clicked on, don't allow any additional clicks until
418     // the state is reset (when login screen reappears).
419     is_launch_enabled_ = false;
420 
421     launch_app_callback_.Run(kiosk_apps_[command_id]);
422   }
423 
OnMenuWillShow(SimpleMenuModel * source)424   void OnMenuWillShow(SimpleMenuModel* source) override { on_show_menu_.Run(); }
425 
IsCommandIdChecked(int command_id) const426   bool IsCommandIdChecked(int command_id) const override { return false; }
427 
IsCommandIdEnabled(int command_id) const428   bool IsCommandIdEnabled(int command_id) const override { return true; }
429 
430  private:
431   base::RepeatingCallback<void(const KioskAppMenuEntry&)> launch_app_callback_;
432   base::RepeatingCallback<void()> on_show_menu_;
433   std::unique_ptr<views::MenuRunner> menu_runner_;
434   std::vector<KioskAppMenuEntry> kiosk_apps_;
435 
436   bool is_launch_enabled_ = true;
437 
438   DISALLOW_COPY_AND_ASSIGN(KioskAppsButton);
439 };
440 
441 // Class that temporarily disables Guest login buttin on shelf.
442 class LoginShelfView::ScopedGuestButtonBlockerImpl
443     : public ScopedGuestButtonBlocker {
444  public:
ScopedGuestButtonBlockerImpl(base::WeakPtr<LoginShelfView> shelf_view)445   ScopedGuestButtonBlockerImpl(base::WeakPtr<LoginShelfView> shelf_view)
446       : shelf_view_(shelf_view) {
447     ++(shelf_view_->scoped_guest_button_blockers_);
448     if (shelf_view_->scoped_guest_button_blockers_ == 1)
449       shelf_view_->UpdateUi();
450   }
451 
~ScopedGuestButtonBlockerImpl()452   ~ScopedGuestButtonBlockerImpl() override {
453     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
454     if (!shelf_view_)
455       return;
456 
457     DCHECK_GT(shelf_view_->scoped_guest_button_blockers_, 0);
458     --(shelf_view_->scoped_guest_button_blockers_);
459     if (!shelf_view_->scoped_guest_button_blockers_)
460       shelf_view_->UpdateUi();
461   }
462 
463  private:
464   SEQUENCE_CHECKER(sequence_checker_);
465 
466   // ScopedGuestButtonBlockerImpl is not owned by the LoginShelfView,
467   // so they could be independently destroyed.
468   base::WeakPtr<LoginShelfView> shelf_view_;
469 };
470 
471 LoginShelfView::TestUiUpdateDelegate::~TestUiUpdateDelegate() = default;
472 
LoginShelfView(LockScreenActionBackgroundController * lock_screen_action_background)473 LoginShelfView::LoginShelfView(
474     LockScreenActionBackgroundController* lock_screen_action_background)
475     : lock_screen_action_background_(lock_screen_action_background) {
476   // We reuse the focusable state on this view as a signal that focus should
477   // switch to the lock screen or status area. This view should otherwise not
478   // be focusable.
479   SetFocusBehavior(FocusBehavior::ALWAYS);
480   SetLayoutManager(std::make_unique<views::BoxLayout>(
481       views::BoxLayout::Orientation::kHorizontal));
482 
483   auto add_button = [this](ButtonId id, base::RepeatingClosure callback,
484                            int text_resource_id, const gfx::VectorIcon& icon) {
485     LoginShelfButton* button = new LoginShelfButton(
486         base::BindRepeating(&ButtonPressed, id, std::move(callback)),
487         text_resource_id, icon);
488     button->SetID(id);
489     AddChildView(button);
490   };
491   const auto shutdown_restart_callback = base::BindRepeating(
492       &LockStateController::RequestShutdown,
493       base::Unretained(Shell::Get()->lock_state_controller()),
494       ShutdownReason::LOGIN_SHUT_DOWN_BUTTON);
495   add_button(kShutdown, shutdown_restart_callback,
496              IDS_ASH_SHELF_SHUTDOWN_BUTTON, kShelfShutdownButtonIcon);
497   add_button(kRestart, std::move(shutdown_restart_callback),
498              IDS_ASH_SHELF_RESTART_BUTTON, kShelfShutdownButtonIcon);
499   add_button(
500       kSignOut, base::BindRepeating([]() {
501         base::RecordAction(base::UserMetricsAction("ScreenLocker_Signout"));
502         Shell::Get()->session_controller()->RequestSignOut();
503       }),
504       IDS_ASH_SHELF_SIGN_OUT_BUTTON, kShelfSignOutButtonIcon);
505   kiosk_apps_button_ = new KioskAppsButton();
506   kiosk_apps_button_->SetID(kApps);
507   AddChildView(kiosk_apps_button_);
508   add_button(kCloseNote,
509              base::BindRepeating(
510                  &TrayAction::CloseLockScreenNote,
511                  base::Unretained(Shell::Get()->tray_action()),
512                  mojom::CloseLockScreenNoteReason::kUnlockButtonPressed),
513              IDS_ASH_SHELF_UNLOCK_BUTTON, kShelfUnlockButtonIcon);
514   add_button(kCancel,
515              base::BindRepeating(
516                  [](LoginShelfView* shelf) {
517                    // If the Cancel button has focus, clear it. Otherwise the
518                    // shelf within active session may still be focused.
519                    shelf->GetFocusManager()->ClearFocus();
520                    Shell::Get()->login_screen_controller()->CancelAddUser();
521                  },
522                  this),
523              IDS_ASH_SHELF_CANCEL_BUTTON, kShelfCancelButtonIcon);
524   add_button(kBrowseAsGuest,
525              base::BindRepeating(
526                  &LoginScreenController::LoginAsGuest,
527                  base::Unretained(Shell::Get()->login_screen_controller())),
528              IDS_ASH_BROWSE_AS_GUEST_BUTTON, kShelfBrowseAsGuestButtonIcon);
529   add_button(kAddUser,
530              base::BindRepeating(
531                  &LoginScreenController::ShowGaiaSignin,
532                  base::Unretained(Shell::Get()->login_screen_controller()),
533                  EmptyAccountId()),
534              IDS_ASH_ADD_USER_BUTTON, kShelfAddPersonButtonIcon);
535   add_button(kParentAccess, base::BindRepeating([]() {
536                // TODO(https://crbug.com/999387): Remove this when handling
537                // touch cancellation is fixed for system modal windows.
538                base::ThreadTaskRunnerHandle::Get()->PostTask(
539                    FROM_HERE, base::BindOnce([]() {
540                      LockScreen::Get()->ShowParentAccessDialog();
541                    }));
542              }),
543              IDS_ASH_PARENT_ACCESS_BUTTON, kPinRequestLockIcon);
544   add_button(kEnterpriseEnrollment,
545              base::BindRepeating(
546                  &LoginScreenController::HandleAccelerator,
547                  base::Unretained(Shell::Get()->login_screen_controller()),
548                  ash::LoginAcceleratorAction::kStartEnrollment),
549              IDS_ASH_ENTERPRISE_ENROLLMENT_BUTTON, chromeos::kEnterpriseIcon);
550 
551   // Adds observers for states that affect the visiblity of different buttons.
552   tray_action_observer_.Add(Shell::Get()->tray_action());
553   shutdown_controller_observer_.Add(Shell::Get()->shutdown_controller());
554   lock_screen_action_background_observer_.Add(lock_screen_action_background);
555   login_data_dispatcher_observer_.Add(
556       Shell::Get()->login_screen_controller()->data_dispatcher());
557   UpdateUi();
558 }
559 
560 LoginShelfView::~LoginShelfView() = default;
561 
UpdateAfterSessionChange()562 void LoginShelfView::UpdateAfterSessionChange() {
563   UpdateUi();
564 }
565 
GetClassName() const566 const char* LoginShelfView::GetClassName() const {
567   return "LoginShelfView";
568 }
569 
OnFocus()570 void LoginShelfView::OnFocus() {
571   LOG(WARNING) << "LoginShelfView was focused, but this should never happen. "
572                   "Forwarded focus to shelf widget with an unknown direction.";
573   Shell::Get()->focus_cycler()->FocusWidget(
574       Shelf::ForWindow(GetWidget()->GetNativeWindow())->shelf_widget());
575 }
576 
AboutToRequestFocusFromTabTraversal(bool reverse)577 void LoginShelfView::AboutToRequestFocusFromTabTraversal(bool reverse) {
578   if (reverse) {
579     // Focus should leave the system tray.
580     Shell::Get()->system_tray_notifier()->NotifyFocusOut(reverse);
581   } else {
582     // Focus goes to status area.
583     StatusAreaWidget* status_area_widget =
584         Shelf::ForWindow(GetWidget()->GetNativeWindow())->GetStatusAreaWidget();
585     status_area_widget->status_area_widget_delegate()
586         ->set_default_last_focusable_child(reverse);
587     Shell::Get()->focus_cycler()->FocusWidget(status_area_widget);
588   }
589 }
590 
GetAccessibleNodeData(ui::AXNodeData * node_data)591 void LoginShelfView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
592   if (LockScreen::HasInstance()) {
593     GetViewAccessibility().OverridePreviousFocus(LockScreen::Get()->widget());
594   }
595 
596   Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow());
597 
598   GetViewAccessibility().OverrideNextFocus(shelf->GetStatusAreaWidget());
599   node_data->role = ax::mojom::Role::kToolbar;
600   node_data->SetName(l10n_util::GetStringUTF8(IDS_ASH_SHELF_ACCESSIBLE_NAME));
601 }
602 
Layout()603 void LoginShelfView::Layout() {
604   views::View::Layout();
605   UpdateButtonUnionBounds();
606 }
607 
LaunchAppForTesting(const std::string & app_id)608 bool LoginShelfView::LaunchAppForTesting(const std::string& app_id) {
609   return kiosk_apps_button_->GetEnabled() &&
610          kiosk_apps_button_->LaunchAppForTesting(app_id);
611 }
612 
InstallTestUiUpdateDelegate(std::unique_ptr<TestUiUpdateDelegate> delegate)613 void LoginShelfView::InstallTestUiUpdateDelegate(
614     std::unique_ptr<TestUiUpdateDelegate> delegate) {
615   DCHECK(!test_ui_update_delegate_.get());
616   test_ui_update_delegate_ = std::move(delegate);
617 }
618 
SetKioskApps(const std::vector<KioskAppMenuEntry> & kiosk_apps,const base::RepeatingCallback<void (const KioskAppMenuEntry &)> & launch_app,const base::RepeatingCallback<void ()> & on_show_menu)619 void LoginShelfView::SetKioskApps(
620     const std::vector<KioskAppMenuEntry>& kiosk_apps,
621     const base::RepeatingCallback<void(const KioskAppMenuEntry&)>& launch_app,
622     const base::RepeatingCallback<void()>& on_show_menu) {
623   kiosk_apps_button_->SetApps(kiosk_apps, launch_app, on_show_menu);
624   UpdateUi();
625 }
626 
SetLoginDialogState(OobeDialogState state)627 void LoginShelfView::SetLoginDialogState(OobeDialogState state) {
628   dialog_state_ = state;
629   UpdateUi();
630 }
631 
SetAllowLoginAsGuest(bool allow_guest)632 void LoginShelfView::SetAllowLoginAsGuest(bool allow_guest) {
633   allow_guest_ = allow_guest;
634   UpdateUi();
635 }
636 
ShowParentAccessButton(bool show)637 void LoginShelfView::ShowParentAccessButton(bool show) {
638   show_parent_access_ = show;
639   UpdateUi();
640 }
641 
SetIsFirstSigninStep(bool is_first)642 void LoginShelfView::SetIsFirstSigninStep(bool is_first) {
643   is_first_signin_step_ = is_first;
644   UpdateUi();
645 }
646 
SetAddUserButtonEnabled(bool enable_add_user)647 void LoginShelfView::SetAddUserButtonEnabled(bool enable_add_user) {
648   GetViewByID(kAddUser)->SetEnabled(enable_add_user);
649 }
650 
SetShutdownButtonEnabled(bool enable_shutdown_button)651 void LoginShelfView::SetShutdownButtonEnabled(bool enable_shutdown_button) {
652   GetViewByID(kShutdown)->SetEnabled(enable_shutdown_button);
653 }
SetButtonOpacity(float target_opacity)654 void LoginShelfView::SetButtonOpacity(float target_opacity) {
655   static constexpr ButtonId kButtonIds[] = {
656       kShutdown,
657       kRestart,
658       kSignOut,
659       kCloseNote,
660       kCancel,
661       kParentAccess,
662       kBrowseAsGuest,
663       kAddUser,
664       kEnterpriseEnrollment
665   };
666   for (const auto& button_id : kButtonIds) {
667     AnimateButtonOpacity(GetViewByID(button_id)->layer(), target_opacity,
668                          ShelfConfig::Get()->DimAnimationDuration(),
669                          ShelfConfig::Get()->DimAnimationTween());
670   }
671   AnimateButtonOpacity(kiosk_apps_button_->layer(), target_opacity,
672                        ShelfConfig::Get()->DimAnimationDuration(),
673                        ShelfConfig::Get()->DimAnimationTween());
674 }
675 
676 std::unique_ptr<ScopedGuestButtonBlocker>
GetScopedGuestButtonBlocker()677 LoginShelfView::GetScopedGuestButtonBlocker() {
678   return std::make_unique<LoginShelfView::ScopedGuestButtonBlockerImpl>(
679       weak_ptr_factory_.GetWeakPtr());
680 }
681 
OnLockScreenNoteStateChanged(mojom::TrayActionState state)682 void LoginShelfView::OnLockScreenNoteStateChanged(
683     mojom::TrayActionState state) {
684   UpdateUi();
685 }
686 
OnLockScreenActionBackgroundStateChanged(LockScreenActionBackgroundState state)687 void LoginShelfView::OnLockScreenActionBackgroundStateChanged(
688     LockScreenActionBackgroundState state) {
689   UpdateUi();
690 }
691 
OnShutdownPolicyChanged(bool reboot_on_shutdown)692 void LoginShelfView::OnShutdownPolicyChanged(bool reboot_on_shutdown) {
693   UpdateUi();
694 }
695 
OnUsersChanged(const std::vector<LoginUserInfo> & users)696 void LoginShelfView::OnUsersChanged(const std::vector<LoginUserInfo>& users) {
697   login_screen_has_users_ = !users.empty();
698   UpdateUi();
699 }
700 
OnOobeDialogStateChanged(OobeDialogState state)701 void LoginShelfView::OnOobeDialogStateChanged(OobeDialogState state) {
702   SetLoginDialogState(state);
703 }
704 
HandleLocaleChange()705 void LoginShelfView::HandleLocaleChange() {
706   for (views::View* child : children()) {
707     if (child->GetClassName() == kLoginShelfButtonClassName) {
708       auto* button = static_cast<LoginShelfButton*>(child);
709       button->SetText(l10n_util::GetStringUTF16(button->text_resource_id()));
710       button->SetAccessibleName(button->GetText());
711     }
712   }
713 }
714 
LockScreenActionBackgroundAnimating() const715 bool LoginShelfView::LockScreenActionBackgroundAnimating() const {
716   return lock_screen_action_background_->state() ==
717              LockScreenActionBackgroundState::kShowing ||
718          lock_screen_action_background_->state() ==
719              LockScreenActionBackgroundState::kHiding;
720 }
721 
UpdateUi()722 void LoginShelfView::UpdateUi() {
723   // Make sure observers are notified.
724   base::ScopedClosureRunner fire_observer(base::BindOnce(
725       [](LoginShelfView* self) {
726         if (self->test_ui_update_delegate())
727           self->test_ui_update_delegate()->OnUiUpdate();
728       },
729       base::Unretained(this)));
730 
731   SessionState session_state =
732       Shell::Get()->session_controller()->GetSessionState();
733   if (session_state == SessionState::ACTIVE) {
734     // The entire view was set invisible. The buttons are also set invisible
735     // to avoid affecting calculation of the shelf size.
736     for (auto* child : children())
737       child->SetVisible(false);
738 
739     return;
740   }
741   bool show_reboot = Shell::Get()->shutdown_controller()->reboot_on_shutdown();
742   mojom::TrayActionState tray_action_state =
743       Shell::Get()->tray_action()->GetLockScreenNoteState();
744   bool is_locked = (session_state == SessionState::LOCKED);
745   bool is_lock_screen_note_in_foreground =
746       (tray_action_state == mojom::TrayActionState::kActive ||
747        tray_action_state == mojom::TrayActionState::kLaunching) &&
748       !LockScreenActionBackgroundAnimating();
749 
750   GetViewByID(kShutdown)->SetVisible(!show_reboot &&
751                                      !is_lock_screen_note_in_foreground &&
752                                      !ShutdownButtonHidden(dialog_state_));
753   GetViewByID(kRestart)->SetVisible(show_reboot &&
754                                     !is_lock_screen_note_in_foreground &&
755                                     !ShutdownButtonHidden(dialog_state_));
756   GetViewByID(kSignOut)->SetVisible(is_locked &&
757                                     !is_lock_screen_note_in_foreground);
758   GetViewByID(kCloseNote)
759       ->SetVisible(is_locked && is_lock_screen_note_in_foreground);
760   GetViewByID(kCancel)->SetVisible(session_state ==
761                                    SessionState::LOGIN_SECONDARY);
762   GetViewByID(kParentAccess)->SetVisible(is_locked && show_parent_access_);
763 
764   bool is_login_primary = (session_state == SessionState::LOGIN_PRIMARY);
765   bool dialog_visible = dialog_state_ != OobeDialogState::HIDDEN;
766   bool is_oobe = (session_state == SessionState::OOBE);
767 
768   GetViewByID(kBrowseAsGuest)->SetVisible(ShouldShowGuestButton());
769   GetViewByID(kEnterpriseEnrollment)
770       ->SetVisible(ShouldShowEnterpriseEnrollmentButton());
771 
772   // Show add user button when it's in login screen and Oobe UI dialog is not
773   // visible. The button should not appear if the device is not connected to a
774   // network.
775   GetViewByID(kAddUser)->SetVisible(!dialog_visible && is_login_primary);
776   kiosk_apps_button_->SetVisible(kiosk_apps_button_->HasApps() &&
777                                  ShouldShowAppsButton());
778 
779   // If there is no visible (and thus focusable) buttons, we shouldn't focus
780   // LoginShelfView. We update it here, so we don't need to check visibility
781   // every time we move focus to system tray.
782   bool is_anything_focusable = false;
783   for (auto* child : children()) {
784     if (child->IsFocusable()) {
785       is_anything_focusable = true;
786       break;
787     }
788   }
789   SetFocusBehavior(is_anything_focusable ? views::View::FocusBehavior::ALWAYS
790                                          : views::View::FocusBehavior::NEVER);
791   UpdateButtonColors(is_oobe);
792   Layout();
793 }
794 
UpdateButtonColors(bool use_dark_colors)795 void LoginShelfView::UpdateButtonColors(bool use_dark_colors) {
796   if (use_dark_colors) {
797     static_cast<LoginShelfButton*>(GetViewByID(kShutdown))->PaintDarkColors();
798     static_cast<LoginShelfButton*>(GetViewByID(kRestart))->PaintDarkColors();
799     static_cast<LoginShelfButton*>(GetViewByID(kSignOut))->PaintDarkColors();
800     static_cast<LoginShelfButton*>(GetViewByID(kCloseNote))->PaintDarkColors();
801     static_cast<LoginShelfButton*>(GetViewByID(kCancel))->PaintDarkColors();
802     static_cast<LoginShelfButton*>(GetViewByID(kParentAccess))
803         ->PaintDarkColors();
804     static_cast<LoginShelfButton*>(GetViewByID(kBrowseAsGuest))
805         ->PaintDarkColors();
806     static_cast<LoginShelfButton*>(GetViewByID(kAddUser))->PaintDarkColors();
807     static_cast<LoginShelfButton*>(GetViewByID(kEnterpriseEnrollment))
808         ->PaintDarkColors();
809     kiosk_apps_button_->PaintDarkColors();
810   } else {
811     static_cast<LoginShelfButton*>(GetViewByID(kShutdown))->PaintLightColors();
812     static_cast<LoginShelfButton*>(GetViewByID(kRestart))->PaintLightColors();
813     static_cast<LoginShelfButton*>(GetViewByID(kSignOut))->PaintLightColors();
814     static_cast<LoginShelfButton*>(GetViewByID(kCloseNote))->PaintLightColors();
815     static_cast<LoginShelfButton*>(GetViewByID(kCancel))->PaintLightColors();
816     static_cast<LoginShelfButton*>(GetViewByID(kParentAccess))
817         ->PaintLightColors();
818     static_cast<LoginShelfButton*>(GetViewByID(kBrowseAsGuest))
819         ->PaintLightColors();
820     static_cast<LoginShelfButton*>(GetViewByID(kAddUser))->PaintLightColors();
821     static_cast<LoginShelfButton*>(GetViewByID(kEnterpriseEnrollment))
822         ->PaintLightColors();
823     kiosk_apps_button_->PaintLightColors();
824   }
825 }
826 
UpdateButtonUnionBounds()827 void LoginShelfView::UpdateButtonUnionBounds() {
828   button_union_bounds_ = gfx::Rect();
829   View::Views children = GetChildrenInZOrder();
830   for (auto* child : children) {
831     if (child->GetVisible())
832       button_union_bounds_.Union(child->bounds());
833   }
834 }
835 
ShouldShowGuestAndAppsButtons() const836 bool LoginShelfView::ShouldShowGuestAndAppsButtons() const {
837   bool dialog_state_allowed = false;
838   if (dialog_state_ == OobeDialogState::USER_CREATION ||
839       dialog_state_ == OobeDialogState::GAIA_SIGNIN) {
840     dialog_state_allowed = !login_screen_has_users_ && is_first_signin_step_;
841   } else if (dialog_state_ == OobeDialogState::ERROR ||
842              dialog_state_ == OobeDialogState::HIDDEN) {
843     dialog_state_allowed = true;
844   }
845 
846   const bool user_session_started =
847       Shell::Get()->session_controller()->NumberOfLoggedInUsers() != 0;
848 
849   return dialog_state_allowed && !user_session_started;
850 }
851 
852 // Show guest button if:
853 // 1. Guest login is allowed.
854 // 2. OOBE UI dialog is currently showing the login UI or error.
855 // 3. No users sessions have started. Button is hidden from all post login
856 // screens like sync consent, etc.
857 // 4. It's in login screen or OOBE. Note: In OOBE, the guest button visibility
858 // is manually controlled by the WebUI.
859 // 5. OOBE UI dialog is not currently showing gaia signin screen, or if there
860 // are no user views available. If there are no user pods (i.e. Gaia is the
861 // only signin option), the guest button should be shown if allowed by policy
862 // and OOBE.
863 // 6. There are no scoped guest buttons blockers active.
ShouldShowGuestButton() const864 bool LoginShelfView::ShouldShowGuestButton() const {
865   if (!allow_guest_)
866     return false;
867 
868   if (scoped_guest_button_blockers_ > 0)
869     return false;
870 
871   if (!ShouldShowGuestAndAppsButtons())
872     return false;
873 
874   const SessionState session_state =
875       Shell::Get()->session_controller()->GetSessionState();
876 
877   if (session_state == SessionState::OOBE)
878     return is_first_signin_step_;
879 
880   if (session_state != SessionState::LOGIN_PRIMARY)
881     return false;
882 
883   return true;
884 }
885 
ShouldShowEnterpriseEnrollmentButton() const886 bool LoginShelfView::ShouldShowEnterpriseEnrollmentButton() const {
887   const SessionState session_state =
888       Shell::Get()->session_controller()->GetSessionState();
889   return session_state == SessionState::OOBE &&
890          dialog_state_ == OobeDialogState::USER_CREATION;
891 }
892 
ShouldShowAppsButton() const893 bool LoginShelfView::ShouldShowAppsButton() const {
894   if (!ShouldShowGuestAndAppsButtons())
895     return false;
896 
897   const SessionState session_state =
898       Shell::Get()->session_controller()->GetSessionState();
899   if (session_state != SessionState::LOGIN_PRIMARY)
900     return false;
901 
902   return true;
903 }
904 
905 }  // namespace ash
906