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