1 // Copyright 2020 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/in_session_auth/auth_dialog_contents_view.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "ash/login/resources/grit/login_resources.h"
11 #include "ash/login/ui/horizontal_image_sequence_animation_decoder.h"
12 #include "ash/login/ui/login_password_view.h"
13 #include "ash/login/ui/login_pin_input_view.h"
14 #include "ash/login/ui/login_pin_view.h"
15 #include "ash/login/ui/non_accessible_view.h"
16 #include "ash/login/ui/views_utils.h"
17 #include "ash/public/cpp/in_session_auth_dialog_controller.h"
18 #include "ash/resources/vector_icons/vector_icons.h"
19 #include "ash/strings/grit/ash_strings.h"
20 #include "base/callback_helpers.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/timer/timer.h"
23 #include "ui/accessibility/ax_enums.mojom.h"
24 #include "ui/accessibility/ax_node_data.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/paint_vector_icon.h"
28 #include "ui/views/background.h"
29 #include "ui/views/bubble/bubble_border.h"
30 #include "ui/views/controls/button/md_text_button.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/layout/box_layout.h"
33 #include "ui/views/layout/fill_layout.h"
34 
35 namespace ash {
36 namespace {
37 
38 constexpr int kContainerPreferredWidth = 340;
39 
40 constexpr int kBorderTopDp = 36;
41 constexpr int kBorderLeftDp = 24;
42 constexpr int kBorderBottomDp = 20;
43 constexpr int kBorderRightDp = 24;
44 constexpr int kCornerRadius = 12;
45 
46 constexpr int kTitleFontSizeDeltaDp = 4;
47 constexpr int kOriginNameLineHeight = 18;
48 
49 constexpr int kSpacingAfterAvatar = 18;
50 constexpr int kSpacingAfterTitle = 8;
51 constexpr int kSpacingAfterOriginName = 32;
52 constexpr int kSpacingAfterInputField = 16;
53 
54 constexpr int kAvatarSizeDp = 36;
55 constexpr int kFingerprintIconSizeDp = 28;
56 constexpr int kSpacingBetweenPinPadAndFingerprintIcon = 24;
57 constexpr int kSpacingBetweenFingerprintIconAndLabelDp = 15;
58 constexpr int kFingerprintViewWidthDp = 204;
59 constexpr int kFingerprintFailedAnimationNumFrames = 45;
60 constexpr base::TimeDelta kResetToDefaultIconDelay =
61     base::TimeDelta::FromMilliseconds(1300);
62 constexpr base::TimeDelta kResetToDefaultMessageDelay =
63     base::TimeDelta::FromMilliseconds(3000);
64 constexpr base::TimeDelta kFingerprintFailedAnimationDuration =
65     base::TimeDelta::FromMilliseconds(700);
66 
67 // 38% opacity.
68 constexpr SkColor kDisabledFingerprintIconColor =
69     SkColorSetA(gfx::kGoogleGrey900, 97);
70 constexpr SkColor kBackgroundColor = SK_ColorWHITE;
71 constexpr SkColor kTextColorSecondary = gfx::kGoogleGrey700;
72 constexpr SkColor kTextColorPrimary = gfx::kGoogleGrey900;
73 constexpr SkColor kErrorColor = gfx::kGoogleRed600;
74 
75 constexpr int kSpacingBeforeButtons = 32;
76 
77 constexpr int kMaxPinAttempts = 5;
78 
79 }  // namespace
80 
81 // Consists of fingerprint icon view and a label.
82 class AuthDialogContentsView::FingerprintView : public views::View {
83  public:
84   // Use a subclass that inherit views::Label so that GetAccessibleNodeData
85   // override is respected.
86   class FingerprintLabel : public views::Label {
87    public:
88     // views::View
GetAccessibleNodeData(ui::AXNodeData * node_data)89     void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
90       node_data->role = ax::mojom::Role::kStaticText;
91       node_data->SetName(accessible_name_);
92     }
93 
SetAccessibleName(const base::string16 & name)94     void SetAccessibleName(const base::string16& name) {
95       accessible_name_ = name;
96       NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged,
97                                true /*send_native_event*/);
98     }
99 
100    private:
101     base::string16 accessible_name_;
102   };
103 
FingerprintView()104   FingerprintView() {
105     auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
106         views::BoxLayout::Orientation::kVertical, gfx::Insets(),
107         kSpacingBetweenFingerprintIconAndLabelDp));
108     layout->set_main_axis_alignment(
109         views::BoxLayout::MainAxisAlignment::kCenter);
110 
111     icon_ = AddChildView(std::make_unique<AnimatedRoundedImageView>(
112         gfx::Size(kFingerprintIconSizeDp, kFingerprintIconSizeDp),
113         0 /*corner_radius*/));
114 
115     label_ = AddChildView(std::make_unique<FingerprintLabel>());
116     label_->SetSubpixelRenderingEnabled(false);
117     label_->SetAutoColorReadabilityEnabled(false);
118     label_->SetEnabledColor(kTextColorPrimary);
119     label_->SetMultiLine(true);
120     label_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
121 
122     DisplayCurrentState();
123   }
124   FingerprintView(const FingerprintView&) = delete;
125   FingerprintView& operator=(const FingerprintView&) = delete;
126   ~FingerprintView() override = default;
127 
SetState(FingerprintState state)128   void SetState(FingerprintState state) {
129     if (state_ == state)
130       return;
131 
132     state_ = state;
133     DisplayCurrentState();
134   }
135 
SetCanUsePin(bool can_use_pin)136   void SetCanUsePin(bool can_use_pin) {
137     if (can_use_pin_ == can_use_pin)
138       return;
139 
140     can_use_pin_ = can_use_pin;
141     DisplayCurrentState();
142   }
143 
144   // Notify the user of the fingerprint auth result. Should be called after
145   // SetState. If fingerprint auth failed and retry is allowed, reset to
146   // default state after animation.
NotifyFingerprintAuthResult(bool success)147   void NotifyFingerprintAuthResult(bool success) {
148     reset_state_.Stop();
149     if (state_ == FingerprintState::DISABLED_FROM_ATTEMPTS) {
150       label_->SetText(l10n_util::GetStringUTF16(
151           IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_DISABLED_FROM_ATTEMPTS));
152       label_->SetAccessibleName(l10n_util::GetStringUTF16(
153           IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_DISABLED_FROM_ATTEMPTS));
154     } else if (success) {
155       label_->SetText(l10n_util::GetStringUTF16(
156           IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_SUCCESS));
157       label_->SetAccessibleName(l10n_util::GetStringUTF16(
158           IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_SUCCESS));
159     } else {
160       label_->SetText(l10n_util::GetStringUTF16(
161           IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_FAILED));
162       label_->SetAccessibleName(l10n_util::GetStringUTF16(
163           IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_FAILED));
164     }
165 
166     if (!success) {
167       // This is just to display the "fingerprint auth failure" animation. It
168       // does not necessarily mean |state_| is DISABLED_FROM_ATTEMPTS.
169       SetIcon(FingerprintState::DISABLED_FROM_ATTEMPTS);
170       // base::Unretained is safe because reset_state_ is owned by |this|.
171       reset_state_.Start(FROM_HERE, kResetToDefaultIconDelay,
172                          base::BindOnce(&FingerprintView::DisplayCurrentState,
173                                         base::Unretained(this)));
174       label_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert,
175                                        true /*send_native_event*/);
176     }
177   }
178 
179   // views::View:
CalculatePreferredSize() const180   gfx::Size CalculatePreferredSize() const override {
181     gfx::Size size = views::View::CalculatePreferredSize();
182     size.set_width(kFingerprintViewWidthDp);
183     return size;
184   }
185 
186   // views::View:
OnGestureEvent(ui::GestureEvent * event)187   void OnGestureEvent(ui::GestureEvent* event) override {
188     if (event->type() != ui::ET_GESTURE_TAP)
189       return;
190     if (state_ == FingerprintState::AVAILABLE_DEFAULT ||
191         state_ == FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING) {
192       SetState(FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING);
193       reset_state_.Start(
194           FROM_HERE, kResetToDefaultMessageDelay,
195           base::BindOnce(&FingerprintView::SetState, base::Unretained(this),
196                          FingerprintState::AVAILABLE_DEFAULT));
197     }
198   }
199 
200  private:
DisplayCurrentState()201   void DisplayCurrentState() {
202     SetVisible(state_ != FingerprintState::UNAVAILABLE);
203     SetIcon(state_);
204     if (state_ != FingerprintState::UNAVAILABLE) {
205       base::string16 fingerprint_text =
206           l10n_util::GetStringUTF16(GetTextIdFromState());
207       label_->SetText(fingerprint_text);
208       label_->SetAccessibleName(
209           state_ == FingerprintState::DISABLED_FROM_ATTEMPTS
210               ? l10n_util::GetStringUTF16(
211                     IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_DISABLED_FROM_ATTEMPTS)
212               : fingerprint_text);
213     }
214   }
215 
SetIcon(FingerprintState state)216   void SetIcon(FingerprintState state) {
217     const SkColor color =
218         (state == FingerprintState::AVAILABLE_DEFAULT ||
219                  state == FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING
220              ? kTextColorPrimary
221              : kDisabledFingerprintIconColor);
222     switch (state) {
223       case FingerprintState::UNAVAILABLE:
224       case FingerprintState::AVAILABLE_DEFAULT:
225       case FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING:
226       case FingerprintState::DISABLED_FROM_TIMEOUT:
227         icon_->SetImage(gfx::CreateVectorIcon(kLockScreenFingerprintIcon,
228                                               kFingerprintIconSizeDp, color));
229         break;
230       case FingerprintState::DISABLED_FROM_ATTEMPTS:
231         icon_->SetAnimationDecoder(
232             std::make_unique<HorizontalImageSequenceAnimationDecoder>(
233                 *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
234                     IDR_LOGIN_FINGERPRINT_UNLOCK_SPINNER),
235                 kFingerprintFailedAnimationDuration,
236                 kFingerprintFailedAnimationNumFrames),
237             AnimatedRoundedImageView::Playback::kSingle);
238         break;
239     }
240   }
241 
GetTextIdFromState() const242   int GetTextIdFromState() const {
243     switch (state_) {
244       case FingerprintState::AVAILABLE_DEFAULT:
245         return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_AVAILABLE;
246       case FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING:
247         return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_TOUCH_SENSOR;
248       case FingerprintState::DISABLED_FROM_ATTEMPTS:
249         return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_DISABLED_FROM_ATTEMPTS;
250       case FingerprintState::DISABLED_FROM_TIMEOUT:
251         if (can_use_pin_)
252           return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_PIN_OR_PASSWORD_REQUIRED;
253         return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_PASSWORD_REQUIRED;
254       case FingerprintState::UNAVAILABLE:
255         NOTREACHED();
256         return 0;
257     }
258   }
259 
260   FingerprintLabel* label_ = nullptr;
261   AnimatedRoundedImageView* icon_ = nullptr;
262   FingerprintState state_ = FingerprintState::AVAILABLE_DEFAULT;
263   bool can_use_pin_ = false;
264   base::OneShotTimer reset_state_;
265 };
266 
267 class AuthDialogContentsView::TitleLabel : public views::Label {
268  public:
TitleLabel()269   TitleLabel() {
270     SetSubpixelRenderingEnabled(false);
271     SetAutoColorReadabilityEnabled(false);
272     SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
273 
274     const gfx::FontList& base_font_list = views::Label::GetDefaultFontList();
275 
276     SetFontList(base_font_list.Derive(kTitleFontSizeDeltaDp,
277                                       gfx::Font::FontStyle::NORMAL,
278                                       gfx::Font::Weight::MEDIUM));
279     SetMaximumWidth(kContainerPreferredWidth);
280     SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
281 
282     SetPreferredSize(gfx::Size(kContainerPreferredWidth,
283                                GetHeightForWidth(kContainerPreferredWidth)));
284     SetHorizontalAlignment(gfx::ALIGN_CENTER);
285   }
286 
IsShowingError() const287   bool IsShowingError() const { return is_showing_error_; }
288 
ShowTitle()289   void ShowTitle() {
290     base::string16 title =
291         l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_TITLE);
292     SetText(title);
293     SetEnabledColor(kTextColorPrimary);
294     is_showing_error_ = false;
295     SetAccessibleName(title);
296   }
297 
ShowError(const base::string16 & error_text)298   void ShowError(const base::string16& error_text) {
299     SetText(error_text);
300     SetEnabledColor(kErrorColor);
301     is_showing_error_ = true;
302     SetAccessibleName(error_text);
303     NotifyAccessibilityEvent(ax::mojom::Event::kAlert,
304                              true /*send_native_event*/);
305   }
306 
307   // views::View
GetAccessibleNodeData(ui::AXNodeData * node_data)308   void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
309     node_data->role = ax::mojom::Role::kStaticText;
310     node_data->SetName(accessible_name_);
311   }
312 
313  private:
SetAccessibleName(const base::string16 & name)314   void SetAccessibleName(const base::string16& name) {
315     accessible_name_ = name;
316     NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged,
317                              true /*send_native_event*/);
318   }
319 
320   bool is_showing_error_ = false;
321   base::string16 accessible_name_;
322 };
323 
AuthDialogContentsView(uint32_t auth_methods,const std::string & origin_name,const AuthMethodsMetadata & auth_metadata,const UserAvatar & avatar)324 AuthDialogContentsView::AuthDialogContentsView(
325     uint32_t auth_methods,
326     const std::string& origin_name,
327     const AuthMethodsMetadata& auth_metadata,
328     const UserAvatar& avatar)
329     : auth_methods_(auth_methods),
330       origin_name_(origin_name),
331       auth_metadata_(auth_metadata) {
332   DCHECK(auth_methods_ & kAuthPassword);
333 
334   SetLayoutManager(std::make_unique<views::FillLayout>());
335   auto border = std::make_unique<views::BubbleBorder>(
336       views::BubbleBorder::FLOAT, views::BubbleBorder::BIG_SHADOW,
337       kBackgroundColor);
338   border->SetCornerRadius(kCornerRadius);
339   SetBackground(std::make_unique<views::BubbleBackground>(border.get()));
340   SetBorder(std::move(border));
341 
342   container_ = AddChildView(std::make_unique<NonAccessibleView>());
343   container_->SetBorder(views::CreateEmptyBorder(
344       kBorderTopDp, kBorderLeftDp, kBorderBottomDp, kBorderRightDp));
345 
346   main_layout_ =
347       container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
348           views::BoxLayout::Orientation::kVertical));
349   main_layout_->set_main_axis_alignment(
350       views::BoxLayout::MainAxisAlignment::kStart);
351   main_layout_->set_cross_axis_alignment(
352       views::BoxLayout::CrossAxisAlignment::kCenter);
353 
354   AddAvatarView(avatar);
355   AddVerticalSpacing(kSpacingAfterAvatar);
356   AddTitleView();
357   AddVerticalSpacing(kSpacingAfterTitle);
358   AddOriginNameView();
359   AddVerticalSpacing(kSpacingAfterOriginName);
360   if (auth_methods_ & kAuthPin) {
361     if (LoginPinInputView::IsAutosubmitSupported(
362             auth_metadata_.autosubmit_pin_length)) {
363       pin_autosubmit_on_ = true;
364       AddPinDigitInputView();
365     } else {
366       pin_autosubmit_on_ = false;
367       AddPinTextInputView();
368     }
369     AddVerticalSpacing(kSpacingAfterInputField);
370     // PIN pad is always visible regardless of PIN autosubmit status.
371     AddPinPadView();
372   }
373 
374   if (auth_methods_ & kAuthFingerprint) {
375     if (pin_pad_view_)
376       AddVerticalSpacing(kSpacingBetweenPinPadAndFingerprintIcon);
377 
378     fingerprint_view_ =
379         container_->AddChildView(std::make_unique<FingerprintView>());
380     fingerprint_view_->SetCanUsePin(auth_methods_ & kAuthPin);
381   }
382 
383   AddVerticalSpacing(kSpacingBeforeButtons);
384   AddActionButtonsView();
385 }
386 
387 AuthDialogContentsView::~AuthDialogContentsView() = default;
388 
AddedToWidget()389 void AuthDialogContentsView::AddedToWidget() {
390   if (auth_methods_ & kAuthFingerprint) {
391     // Inject a callback from the contents view so that we can show retry
392     // prompt.
393     InSessionAuthDialogController::Get()->AuthenticateUserWithFingerprint(
394         base::BindOnce(&AuthDialogContentsView::OnFingerprintAuthComplete,
395                        weak_factory_.GetWeakPtr()));
396   }
397 }
398 
AddAvatarView(const UserAvatar & avatar)399 void AuthDialogContentsView::AddAvatarView(const UserAvatar& avatar) {
400   avatar_view_ =
401       container_->AddChildView(std::make_unique<AnimatedRoundedImageView>(
402           gfx::Size(kAvatarSizeDp, kAvatarSizeDp),
403           kAvatarSizeDp / 2 /*corner_radius*/));
404   avatar_view_->SetImage(avatar.image);
405 }
406 
AddTitleView()407 void AuthDialogContentsView::AddTitleView() {
408   title_ = container_->AddChildView(std::make_unique<TitleLabel>());
409   title_->ShowTitle();
410 }
411 
AddOriginNameView()412 void AuthDialogContentsView::AddOriginNameView() {
413   origin_name_view_ =
414       container_->AddChildView(std::make_unique<views::Label>());
415   origin_name_view_->SetEnabledColor(kTextColorSecondary);
416   origin_name_view_->SetSubpixelRenderingEnabled(false);
417   origin_name_view_->SetAutoColorReadabilityEnabled(false);
418   origin_name_view_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
419 
420   origin_name_view_->SetText(
421       l10n_util::GetStringFUTF16(IDS_ASH_IN_SESSION_AUTH_ORIGIN_NAME_PROMPT,
422                                  base::UTF8ToUTF16(origin_name_)));
423   origin_name_view_->SetMaximumWidth(kContainerPreferredWidth);
424   origin_name_view_->SetMultiLine(true);
425   origin_name_view_->SetLineHeight(kOriginNameLineHeight);
426 
427   origin_name_view_->SetPreferredSize(gfx::Size(
428       kContainerPreferredWidth,
429       origin_name_view_->GetHeightForWidth(kContainerPreferredWidth)));
430   origin_name_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
431 }
432 
AddPinTextInputView()433 void AuthDialogContentsView::AddPinTextInputView() {
434   pin_text_input_view_ =
435       container_->AddChildView(std::make_unique<LoginPasswordView>(palette_));
436 
437   pin_text_input_view_->SetPaintToLayer();
438   pin_text_input_view_->layer()->SetFillsBoundsOpaquely(false);
439   pin_text_input_view_->SetDisplayPasswordButtonVisible(true);
440   pin_text_input_view_->SetEnabled(true);
441   pin_text_input_view_->SetEnabledOnEmptyPassword(false);
442   pin_text_input_view_->SetFocusEnabledForChildViews(true);
443   pin_text_input_view_->SetVisible(true);
444 
445   pin_text_input_view_->SetPlaceholderText(
446       l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_PLACEHOLDER));
447 }
448 
AddPinDigitInputView()449 void AuthDialogContentsView::AddPinDigitInputView() {
450   pin_digit_input_view_ =
451       container_->AddChildView(std::make_unique<LoginPinInputView>(palette_));
452   pin_digit_input_view_->UpdateLength(auth_metadata_.autosubmit_pin_length);
453   pin_digit_input_view_->SetVisible(true);
454 }
455 
AddPinPadView()456 void AuthDialogContentsView::AddPinPadView() {
457   DCHECK(auth_methods_ & kAuthPin);
458   if (pin_autosubmit_on_) {
459     pin_pad_view_ = container_->AddChildView(std::make_unique<LoginPinView>(
460         LoginPinView::Style::kAlphanumeric, palette_,
461         base::BindRepeating(&AuthDialogContentsView::OnInsertDigitFromPinPad,
462                             base::Unretained(this)),
463         base::BindRepeating(&AuthDialogContentsView::OnBackspaceFromPinPad,
464                             base::Unretained(this))));
465     pin_digit_input_view_->Init(
466         base::BindRepeating(&AuthDialogContentsView::OnAuthSubmit,
467                             base::Unretained(this)),
468         base::BindRepeating(&AuthDialogContentsView::OnPinTextChanged,
469                             base::Unretained(this)));
470   } else {
471     pin_pad_view_ = container_->AddChildView(std::make_unique<LoginPinView>(
472         LoginPinView::Style::kAlphanumeric, palette_,
473         base::BindRepeating(&AuthDialogContentsView::OnInsertDigitFromPinPad,
474                             base::Unretained(this)),
475         base::BindRepeating(&AuthDialogContentsView::OnBackspaceFromPinPad,
476                             base::Unretained(this)),
477         base::BindRepeating(&LoginPasswordView::SubmitPassword,
478                             base::Unretained(pin_text_input_view_))));
479     pin_text_input_view_->Init(
480         base::BindRepeating(&AuthDialogContentsView::OnAuthSubmit,
481                             base::Unretained(this)),
482         base::BindRepeating(&AuthDialogContentsView::OnPinTextChanged,
483                             base::Unretained(this)),
484         base::DoNothing(), views::Button::PressedCallback());
485   }
486   pin_pad_view_->SetVisible(true);
487 }
488 
OnInsertDigitFromPinPad(int digit)489 void AuthDialogContentsView::OnInsertDigitFromPinPad(int digit) {
490   // Ignore anything if reached max attempts.
491   if (pin_attempts_ >= kMaxPinAttempts)
492     return;
493 
494   if (title_->IsShowingError())
495     title_->ShowTitle();
496 
497   if (pin_autosubmit_on_) {
498     pin_digit_input_view_->InsertDigit(digit);
499   } else {
500     pin_text_input_view_->InsertNumber(digit);
501   }
502 }
503 
OnBackspaceFromPinPad()504 void AuthDialogContentsView::OnBackspaceFromPinPad() {
505   // Ignore anything if reached max attempts.
506   if (pin_attempts_ >= kMaxPinAttempts)
507     return;
508 
509   if (title_->IsShowingError())
510     title_->ShowTitle();
511 
512   if (pin_autosubmit_on_) {
513     pin_digit_input_view_->Backspace();
514   } else {
515     pin_text_input_view_->Backspace();
516   }
517 }
518 
OnPinTextChanged(bool is_empty)519 void AuthDialogContentsView::OnPinTextChanged(bool is_empty) {
520   // If the user is interacting with the input field, restore the title (clear
521   // error message).
522   //
523   // If |is_empty| is true, this call may come from resetting
524   // |pin_text_input_view_| or |pin_digit_input_view_|, when the error message
525   // hasn't been shown and read yet. In this case we don't restore the title.
526   if (title_->IsShowingError() && !is_empty)
527     title_->ShowTitle();
528 
529   pin_pad_view_->OnPasswordTextChanged(is_empty);
530 }
531 
AddVerticalSpacing(int height)532 void AuthDialogContentsView::AddVerticalSpacing(int height) {
533   auto* spacing =
534       container_->AddChildView(std::make_unique<NonAccessibleView>());
535   spacing->SetPreferredSize(gfx::Size(kContainerPreferredWidth, height));
536 }
537 
AddActionButtonsView()538 void AuthDialogContentsView::AddActionButtonsView() {
539   action_view_container_ =
540       container_->AddChildView(std::make_unique<NonAccessibleView>());
541   auto* buttons_layout = action_view_container_->SetLayoutManager(
542       std::make_unique<views::BoxLayout>(
543           views::BoxLayout::Orientation::kHorizontal));
544   buttons_layout->set_main_axis_alignment(
545       views::BoxLayout::MainAxisAlignment::kStart);
546 
547   help_button_ =
548       action_view_container_->AddChildView(std::make_unique<views::LabelButton>(
549           base::BindRepeating(&AuthDialogContentsView::OnNeedHelpButtonPressed,
550                               base::Unretained(this)),
551           l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_HELP),
552           views::style::CONTEXT_BUTTON));
553   help_button_->SetEnabledTextColors(kTextColorPrimary);
554 
555   auto* spacing = action_view_container_->AddChildView(
556       std::make_unique<NonAccessibleView>());
557   buttons_layout->SetFlexForView(spacing, 1);
558 
559   cancel_button_ = action_view_container_->AddChildView(
560       std::make_unique<views::MdTextButton>(
561           base::BindRepeating(&AuthDialogContentsView::OnCancelButtonPressed,
562                               base::Unretained(this)),
563           l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_CANCEL)));
564 
565   action_view_container_->SetPreferredSize(
566       gfx::Size(kContainerPreferredWidth, cancel_button_->height()));
567 }
568 
OnCancelButtonPressed(const ui::Event & event)569 void AuthDialogContentsView::OnCancelButtonPressed(const ui::Event& event) {
570   InSessionAuthDialogController::Get()->Cancel();
571 }
572 
OnNeedHelpButtonPressed(const ui::Event & event)573 void AuthDialogContentsView::OnNeedHelpButtonPressed(const ui::Event& event) {
574   InSessionAuthDialogController::Get()->OpenInSessionAuthHelpPage();
575 }
576 
OnAuthSubmit(const base::string16 & pin)577 void AuthDialogContentsView::OnAuthSubmit(const base::string16& pin) {
578   if (pin_autosubmit_on_) {
579     pin_digit_input_view_->SetReadOnly(true);
580   } else {
581     pin_text_input_view_->SetReadOnly(true);
582   }
583   InSessionAuthDialogController::Get()->AuthenticateUserWithPin(
584       base::UTF16ToUTF8(pin),
585       base::BindOnce(&AuthDialogContentsView::OnPinAuthComplete,
586                      weak_factory_.GetWeakPtr()));
587 }
588 
589 // TODO(b/156258540): Clear PIN if auth failed and retry is allowed.
OnPinAuthComplete(base::Optional<bool> success)590 void AuthDialogContentsView::OnPinAuthComplete(base::Optional<bool> success) {
591   // On success, do nothing, and the dialog will dismiss.
592   if (success.has_value() && success.value())
593     return;
594 
595   pin_attempts_++;
596   base::string16 error_text =
597       pin_attempts_ >= kMaxPinAttempts
598           ? l10n_util::GetStringUTF16(
599                 IDS_ASH_IN_SESSION_AUTH_PIN_TOO_MANY_ATTEMPTS)
600           : l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_INCORRECT);
601   title_->ShowError(error_text);
602 
603   if (pin_attempts_ < kMaxPinAttempts) {
604     if (pin_autosubmit_on_) {
605       pin_digit_input_view_->Reset();
606       pin_digit_input_view_->SetReadOnly(false);
607     } else {
608       pin_text_input_view_->Reset();
609       pin_text_input_view_->SetReadOnly(false);
610     }
611   }
612 }
613 
OnFingerprintAuthComplete(bool success,FingerprintState fingerprint_state)614 void AuthDialogContentsView::OnFingerprintAuthComplete(
615     bool success,
616     FingerprintState fingerprint_state) {
617   fingerprint_view_->SetState(fingerprint_state);
618   // Prepare for the next fingerprint scan.
619   if (!success && fingerprint_state == FingerprintState::AVAILABLE_DEFAULT) {
620     InSessionAuthDialogController::Get()->AuthenticateUserWithFingerprint(
621         base::BindOnce(&AuthDialogContentsView::OnFingerprintAuthComplete,
622                        weak_factory_.GetWeakPtr()));
623   }
624   fingerprint_view_->NotifyFingerprintAuthResult(success);
625 }
626 
GetAccessibleNodeData(ui::AXNodeData * node_data)627 void AuthDialogContentsView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
628   views::View::GetAccessibleNodeData(node_data);
629   node_data->role = ax::mojom::Role::kDialog;
630   node_data->SetName(
631       l10n_util::GetStringFUTF16(IDS_ASH_IN_SESSION_AUTH_ACCESSIBLE_TITLE,
632                                  base::UTF8ToUTF16(origin_name_)));
633 }
634 
RequestFocus()635 void AuthDialogContentsView::RequestFocus() {
636   if (auth_methods_ == kAuthFingerprint) {
637     // There's no PIN input field, so let the focus be on the cancel button
638     // (instead of the help button) because it is more often used.
639     cancel_button_->RequestFocus();
640     return;
641   }
642 
643   // For other cases, the base method correctly sets focus to the input field.
644   views::View::RequestFocus();
645 }
646 
647 }  // namespace ash
648