| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/login/ui/login_auth_user_view.h" |
| |
| #include <map> |
| #include <memory> |
| |
| #include "ash/login/login_screen_controller.h" |
| #include "ash/login/resources/grit/login_resources.h" |
| #include "ash/login/ui/horizontal_image_sequence_animation_decoder.h" |
| #include "ash/login/ui/lock_screen.h" |
| #include "ash/login/ui/login_display_style.h" |
| #include "ash/login/ui/login_password_view.h" |
| #include "ash/login/ui/login_pin_view.h" |
| #include "ash/login/ui/login_user_view.h" |
| #include "ash/login/ui/non_accessible_view.h" |
| #include "ash/login/ui/pin_keyboard_animation.h" |
| #include "ash/login/ui/views_utils.h" |
| #include "ash/public/cpp/login_constants.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/night_light/time_of_day.h" |
| #include "ash/system/toast/toast_manager.h" |
| #include "ash/wallpaper/wallpaper_controller.h" |
| #include "base/bind.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/timer/timer.h" |
| #include "components/user_manager/user.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/compositor/callback_layer_animation_observer.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/layer_animator.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_analysis.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/interpolated_transform.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/button/md_text_button.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/view.h" |
| |
| namespace ash { |
| namespace { |
| |
| constexpr const char kLoginAuthUserViewClassName[] = "LoginAuthUserView"; |
| |
| // Distance between the user view (ie, the icon and name) and the password |
| // textfield. |
| const int kDistanceBetweenUserViewAndPasswordDp = 28; |
| |
| // Distance between the password textfield and the the pin keyboard. |
| const int kDistanceBetweenPasswordFieldAndPinKeyboardDp = 20; |
| |
| // Distance from the end of pin keyboard to the bottom of the big user view. |
| const int kDistanceFromPinKeyboardToBigUserViewBottomDp = 50; |
| |
| // Distance from the top of the user view to the user icon. |
| constexpr int kDistanceFromTopOfBigUserViewToUserIconDp = 54; |
| |
| // The color of the online sign-in message text. |
| constexpr SkColor kOnlineSignInMessageColor = SkColorSetRGB(0xE6, 0x7C, 0x73); |
| |
| // The color of the disabled auth message bubble when the color extracted from |
| // wallpaper is transparent or invalid (i.e. color calculation fails or is |
| // disabled). |
| constexpr SkColor kDisabledAuthMessageBubbleColor = |
| SkColorSetRGB(0x20, 0x21, 0x24); |
| |
| // Date time format containing only the day of the week, for example: "Tuesday". |
| constexpr char kDayOfWeekOnlyTimeFormat[] = "EEEE"; |
| |
| constexpr int kFingerprintIconSizeDp = 32; |
| constexpr int kResetToDefaultIconDelayMs = 1300; |
| constexpr int kFingerprintIconTopSpacingDp = 20; |
| constexpr int kSpacingBetweenFingerprintIconAndLabelDp = 15; |
| constexpr int kFingerprintViewWidthDp = 204; |
| constexpr int kDistanceBetweenPasswordFieldAndFingerprintViewDp = 90; |
| constexpr int kFingerprintFailedAnimationDurationMs = 700; |
| constexpr int kFingerprintFailedAnimationNumFrames = 45; |
| |
| constexpr int kDisabledAuthMessageVerticalBorderDp = 16; |
| constexpr int kDisabledAuthMessageHorizontalBorderDp = 16; |
| constexpr int kDisabledAuthMessageChildrenSpacingDp = 4; |
| constexpr int kDisabledAuthMessageWidthDp = 204; |
| constexpr int kDisabledAuthMessageHeightDp = 98; |
| constexpr int kDisabledAuthMessageIconSizeDp = 24; |
| constexpr int kDisabledAuthMessageTitleFontSizeDeltaDp = 3; |
| constexpr int kDisabledAuthMessageContentsFontSizeDeltaDp = -1; |
| constexpr int kDisabledAuthMessageRoundedCornerRadiusDp = 8; |
| |
| constexpr int kNonEmptyWidthDp = 1; |
| |
| // Returns an observer that will hide |view| when it fires. The observer will |
| // delete itself after firing (by returning true). Make sure to call |
| // |observer->SetActive()| after attaching it. |
| ui::CallbackLayerAnimationObserver* BuildObserverToHideView(views::View* view) { |
| return new ui::CallbackLayerAnimationObserver(base::Bind( |
| [](views::View* view, |
| const ui::CallbackLayerAnimationObserver& observer) { |
| // Don't hide the view if the animation is aborted, as |view| may no |
| // longer be valid. |
| if (observer.aborted_count()) |
| return true; |
| |
| view->SetVisible(false); |
| return true; |
| }, |
| view)); |
| } |
| |
| // Clears the password for the given |LoginPasswordView| instance, hides it, and |
| // then deletes itself. |
| class ClearPasswordAndHideAnimationObserver |
| : public ui::ImplicitAnimationObserver { |
| public: |
| explicit ClearPasswordAndHideAnimationObserver(LoginPasswordView* view) |
| : password_view_(view) {} |
| ~ClearPasswordAndHideAnimationObserver() override = default; |
| |
| // ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { |
| password_view_->Clear(); |
| password_view_->SetVisible(false); |
| delete this; |
| } |
| |
| private: |
| LoginPasswordView* const password_view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ClearPasswordAndHideAnimationObserver); |
| }; |
| |
| void DecorateOnlineSignInMessage(views::LabelButton* label_button) { |
| label_button->SetPaintToLayer(); |
| label_button->layer()->SetFillsBoundsOpaquely(false); |
| label_button->SetImage( |
| views::Button::STATE_NORMAL, |
| CreateVectorIcon(kLockScreenAlertIcon, kOnlineSignInMessageColor)); |
| label_button->SetTextSubpixelRenderingEnabled(false); |
| label_button->SetTextColor(views::Button::STATE_NORMAL, |
| kOnlineSignInMessageColor); |
| label_button->SetTextColor(views::Button::STATE_HOVERED, |
| kOnlineSignInMessageColor); |
| label_button->SetTextColor(views::Button::STATE_PRESSED, |
| kOnlineSignInMessageColor); |
| label_button->SetBorder(views::CreateEmptyBorder(gfx::Insets(9, 0))); |
| } |
| |
| // The label shown below the fingerprint icon. |
| class FingerprintLabel : public views::Label { |
| public: |
| FingerprintLabel() { |
| SetSubpixelRenderingEnabled(false); |
| SetAutoColorReadabilityEnabled(false); |
| SetEnabledColor(login_constants::kAuthMethodsTextColor); |
| |
| SetTextBasedOnState(mojom::FingerprintState::AVAILABLE); |
| } |
| |
| void SetTextBasedOnAuthAttempt(bool success) { |
| SetText(l10n_util::GetStringUTF16( |
| success ? IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_AUTH_SUCCESS |
| : IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_AUTH_FAILED)); |
| SetAccessibleName(l10n_util::GetStringUTF16( |
| success ? IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_ACCESSIBLE_AUTH_SUCCESS |
| : IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_ACCESSIBLE_AUTH_FAILED)); |
| } |
| |
| void SetTextBasedOnState(mojom::FingerprintState state) { |
| auto get_displayed_id = [&]() { |
| switch (state) { |
| case mojom::FingerprintState::UNAVAILABLE: |
| case mojom::FingerprintState::AVAILABLE: |
| return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_AVAILABLE; |
| case mojom::FingerprintState::DISABLED_FROM_ATTEMPTS: |
| return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS; |
| case mojom::FingerprintState::DISABLED_FROM_TIMEOUT: |
| return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_TIMEOUT; |
| } |
| NOTREACHED(); |
| }; |
| |
| auto get_accessible_id = [&]() { |
| if (state == mojom::FingerprintState::DISABLED_FROM_ATTEMPTS) |
| return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_ACCESSIBLE_AUTH_DISABLED_FROM_ATTEMPTS; |
| return get_displayed_id(); |
| }; |
| |
| SetText(l10n_util::GetStringUTF16(get_displayed_id())); |
| SetAccessibleName(l10n_util::GetStringUTF16(get_accessible_id())); |
| } |
| |
| // views::View: |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override { |
| node_data->role = ax::mojom::Role::kStaticText; |
| node_data->SetName(accessible_name_); |
| } |
| |
| private: |
| void SetAccessibleName(const base::string16& name) { |
| accessible_name_ = name; |
| NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, |
| true /*send_native_event*/); |
| } |
| |
| base::string16 accessible_name_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FingerprintLabel); |
| }; |
| |
| // The content needed to render the disabled auth message view. |
| struct LockScreenMessage { |
| base::string16 title; |
| base::string16 content; |
| const gfx::VectorIcon* icon; |
| }; |
| |
| // Returns the message used when the device was locked due to a time window |
| // limit. |
| LockScreenMessage GetWindowLimitMessage(const base::Time& unlock_time) { |
| LockScreenMessage message; |
| message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_FOR_BED_MESSAGE); |
| |
| base::Time local_midnight = base::Time::Now().LocalMidnight(); |
| const std::string time_of_day = TimeOfDay::FromTime(unlock_time).ToString(); |
| |
| if (unlock_time < local_midnight + base::TimeDelta::FromDays(1)) { |
| // Unlock time is today. |
| message.content = l10n_util::GetStringFUTF16( |
| IDS_ASH_LOGIN_COME_BACK_MESSAGE, base::UTF8ToUTF16(time_of_day)); |
| } else if (unlock_time < local_midnight + base::TimeDelta::FromDays(2)) { |
| // Unlock time is tomorrow. |
| message.content = |
| l10n_util::GetStringFUTF16(IDS_ASH_LOGIN_COME_BACK_TOMORROW_MESSAGE, |
| base::UTF8ToUTF16(time_of_day)); |
| } else { |
| message.content = l10n_util::GetStringFUTF16( |
| IDS_ASH_LOGIN_COME_BACK_DAY_OF_WEEK_MESSAGE, |
| base::TimeFormatWithPattern(unlock_time, kDayOfWeekOnlyTimeFormat), |
| base::UTF8ToUTF16(time_of_day)); |
| } |
| message.icon = &kLockScreenTimeLimitMoonIcon; |
| return message; |
| } |
| |
| // Returns the message used when the device was locked due to a time usage |
| // limit. |
| LockScreenMessage GetUsageLimitMessage(const base::TimeDelta& used_time) { |
| LockScreenMessage message; |
| |
| // 1 minute is used instead of 0, because the device is used for a few |
| // milliseconds before locking. |
| if (used_time < base::TimeDelta::FromMinutes(1)) { |
| // The device was locked all day. |
| message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TAKE_BREAK_MESSAGE); |
| message.content = |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_LOCKED_ALL_DAY_MESSAGE); |
| } else { |
| // The usage limit is over. |
| message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_IS_UP_MESSAGE); |
| |
| // TODO(933973): Stop displaying the hours part of the string when duration |
| // is less than 1 hour. Example: change "0 hours, 7 minutes" to "7 minutes". |
| base::string16 used_time_string; |
| if (!base::TimeDurationFormat( |
| used_time, base::DurationFormatWidth::DURATION_WIDTH_WIDE, |
| &used_time_string)) { |
| LOG(ERROR) << "Failed to generate time duration string."; |
| return message; |
| } |
| |
| message.content = l10n_util::GetStringFUTF16( |
| IDS_ASH_LOGIN_SCREEN_TIME_USED_MESSAGE, used_time_string); |
| } |
| message.icon = &kLockScreenTimeLimitTimerIcon; |
| return message; |
| } |
| |
| // Returns the message used when the device was locked due to a time limit |
| // override. |
| LockScreenMessage GetOverrideMessage() { |
| LockScreenMessage message; |
| message.title = |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_FOR_A_BREAK_MESSAGE); |
| message.content = |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_MANUAL_LOCK_MESSAGE); |
| message.icon = &kLockScreenTimeLimitLockIcon; |
| return message; |
| } |
| |
| LockScreenMessage GetLockScreenMessage(mojom::AuthDisabledReason lock_reason, |
| const base::Time& unlock_time, |
| const base::TimeDelta& used_time) { |
| switch (lock_reason) { |
| case mojom::AuthDisabledReason::TIME_WINDOW_LIMIT: |
| return GetWindowLimitMessage(unlock_time); |
| case mojom::AuthDisabledReason::TIME_USAGE_LIMIT: |
| return GetUsageLimitMessage(used_time); |
| case mojom::AuthDisabledReason::TIME_LIMIT_OVERRIDE: |
| return GetOverrideMessage(); |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| } // namespace |
| |
| // Consists of fingerprint icon view and a label. |
| class LoginAuthUserView::FingerprintView : public views::View { |
| public: |
| FingerprintView() { |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| SetBorder(views::CreateEmptyBorder(kFingerprintIconTopSpacingDp, 0, 0, 0)); |
| |
| auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kVertical, gfx::Insets(), |
| kSpacingBetweenFingerprintIconAndLabelDp)); |
| layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| |
| icon_ = new AnimatedRoundedImageView( |
| gfx::Size(kFingerprintIconSizeDp, kFingerprintIconSizeDp), |
| 0 /*corner_radius*/); |
| icon_->SetImage(gfx::CreateVectorIcon( |
| kLockScreenFingerprintIcon, kFingerprintIconSizeDp, SK_ColorWHITE)); |
| AddChildView(icon_); |
| |
| label_ = new FingerprintLabel(); |
| AddChildView(label_); |
| |
| DisplayCurrentState(); |
| } |
| |
| ~FingerprintView() override = default; |
| |
| void SetState(mojom::FingerprintState state) { |
| if (state_ == state) |
| return; |
| |
| reset_state_.Stop(); |
| state_ = state; |
| |
| DisplayCurrentState(); |
| |
| if (ShouldFireChromeVoxAlert(state)) |
| FireAlert(); |
| } |
| |
| void NotifyFingerprintAuthResult(bool success) { |
| reset_state_.Stop(); |
| label_->SetTextBasedOnAuthAttempt(success); |
| |
| if (success) { |
| icon_->SetImage(gfx::CreateVectorIcon(kLockScreenFingerprintSuccessIcon, |
| kFingerprintIconSizeDp, |
| gfx::kGoogleGreenDark500)); |
| } else { |
| SetIcon(mojom::FingerprintState::DISABLED_FROM_ATTEMPTS); |
| // base::Unretained is safe because reset_state_ is owned by |this|. |
| reset_state_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kResetToDefaultIconDelayMs), |
| base::BindRepeating(&FingerprintView::DisplayCurrentState, |
| base::Unretained(this))); |
| |
| FireAlert(); |
| } |
| } |
| |
| // views::View: |
| gfx::Size CalculatePreferredSize() const override { |
| gfx::Size size = views::View::CalculatePreferredSize(); |
| size.set_width(kFingerprintViewWidthDp); |
| return size; |
| } |
| |
| private: |
| void DisplayCurrentState() { |
| SetVisible(state_ != mojom::FingerprintState::UNAVAILABLE && |
| state_ != mojom::FingerprintState::DISABLED_FROM_TIMEOUT); |
| SetIcon(state_); |
| label_->SetTextBasedOnState(state_); |
| } |
| |
| void FireAlert() { |
| label_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, |
| true /*send_native_event*/); |
| } |
| |
| void SetIcon(mojom::FingerprintState state) { |
| switch (state) { |
| case mojom::FingerprintState::UNAVAILABLE: |
| case mojom::FingerprintState::AVAILABLE: |
| case mojom::FingerprintState::DISABLED_FROM_TIMEOUT: |
| icon_->SetImage(gfx::CreateVectorIcon( |
| kLockScreenFingerprintIcon, kFingerprintIconSizeDp, SK_ColorWHITE)); |
| break; |
| case mojom::FingerprintState::DISABLED_FROM_ATTEMPTS: |
| icon_->SetAnimationDecoder( |
| std::make_unique<HorizontalImageSequenceAnimationDecoder>( |
| *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| IDR_LOGIN_FINGERPRINT_UNLOCK_SPINNER), |
| base::TimeDelta::FromMilliseconds( |
| kFingerprintFailedAnimationDurationMs), |
| kFingerprintFailedAnimationNumFrames), |
| AnimatedRoundedImageView::Playback::kSingle); |
| break; |
| } |
| } |
| |
| bool ShouldFireChromeVoxAlert(mojom::FingerprintState state) { |
| return state == mojom::FingerprintState::DISABLED_FROM_ATTEMPTS || |
| state == mojom::FingerprintState::DISABLED_FROM_TIMEOUT; |
| } |
| |
| FingerprintLabel* label_ = nullptr; |
| AnimatedRoundedImageView* icon_ = nullptr; |
| base::OneShotTimer reset_state_; |
| mojom::FingerprintState state_ = mojom::FingerprintState::AVAILABLE; |
| |
| DISALLOW_COPY_AND_ASSIGN(FingerprintView); |
| }; |
| |
| // The message shown to user when the auth method is |AUTH_DISABLED|. |
| class LoginAuthUserView::DisabledAuthMessageView : public views::View { |
| public: |
| DisabledAuthMessageView() { |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kVertical, |
| gfx::Insets(kDisabledAuthMessageVerticalBorderDp, |
| kDisabledAuthMessageHorizontalBorderDp), |
| kDisabledAuthMessageChildrenSpacingDp)); |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| SetPreferredSize( |
| gfx::Size(kDisabledAuthMessageWidthDp, kDisabledAuthMessageHeightDp)); |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| message_icon_ = new views::ImageView(); |
| message_icon_->SetPreferredSize(gfx::Size(kDisabledAuthMessageIconSizeDp, |
| kDisabledAuthMessageIconSizeDp)); |
| message_icon_->SetImage( |
| gfx::CreateVectorIcon(kLockScreenTimeLimitMoonIcon, |
| kDisabledAuthMessageIconSizeDp, SK_ColorWHITE)); |
| AddChildView(message_icon_); |
| |
| auto decorate_label = [](views::Label* label) { |
| label->SetSubpixelRenderingEnabled(false); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetEnabledColor(SK_ColorWHITE); |
| label->SetFocusBehavior(FocusBehavior::ALWAYS); |
| }; |
| message_title_ = |
| new views::Label(base::string16(), views::style::CONTEXT_LABEL, |
| views::style::STYLE_PRIMARY); |
| message_title_->SetFontList( |
| gfx::FontList().Derive(kDisabledAuthMessageTitleFontSizeDeltaDp, |
| gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM)); |
| decorate_label(message_title_); |
| AddChildView(message_title_); |
| |
| message_contents_ = |
| new views::Label(base::string16(), views::style::CONTEXT_LABEL, |
| views::style::STYLE_PRIMARY); |
| message_contents_->SetFontList( |
| gfx::FontList().Derive(kDisabledAuthMessageContentsFontSizeDeltaDp, |
| gfx::Font::NORMAL, gfx::Font::Weight::NORMAL)); |
| decorate_label(message_contents_); |
| message_contents_->SetMultiLine(true); |
| AddChildView(message_contents_); |
| } |
| |
| ~DisabledAuthMessageView() override = default; |
| |
| // Set the parameters needed to render the message. |
| void SetAuthDisabledMessage( |
| const ash::mojom::AuthDisabledDataPtr& auth_disabled_data) { |
| LockScreenMessage message = GetLockScreenMessage( |
| auth_disabled_data->reason, auth_disabled_data->auth_reenabled_time, |
| auth_disabled_data->device_used_time); |
| message_icon_->SetImage(gfx::CreateVectorIcon( |
| *message.icon, kDisabledAuthMessageIconSizeDp, SK_ColorWHITE)); |
| message_title_->SetText(message.title); |
| message_contents_->SetText(message.content); |
| Layout(); |
| } |
| |
| // views::View: |
| void OnPaint(gfx::Canvas* canvas) override { |
| views::View::OnPaint(canvas); |
| |
| cc::PaintFlags flags; |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| SkColor color = Shell::Get()->wallpaper_controller()->GetProminentColor( |
| color_utils::ColorProfile(color_utils::LumaRange::DARK, |
| color_utils::SaturationRange::MUTED)); |
| if (color == kInvalidWallpaperColor || color == SK_ColorTRANSPARENT) |
| color = kDisabledAuthMessageBubbleColor; |
| flags.setColor(color); |
| canvas->DrawRoundRect(GetContentsBounds(), |
| kDisabledAuthMessageRoundedCornerRadiusDp, flags); |
| } |
| void RequestFocus() override { message_title_->RequestFocus(); } |
| |
| private: |
| views::Label* message_title_; |
| views::Label* message_contents_; |
| views::ImageView* message_icon_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DisabledAuthMessageView); |
| }; |
| |
| struct LoginAuthUserView::AnimationState { |
| explicit AnimationState(LoginAuthUserView* view) { |
| non_pin_y_start_in_screen = view->GetBoundsInScreen().y(); |
| pin_start_in_screen = view->pin_view_->GetBoundsInScreen().origin(); |
| |
| had_pin = (view->auth_methods() & LoginAuthUserView::AUTH_PIN) != 0; |
| had_password = |
| (view->auth_methods() & LoginAuthUserView::AUTH_PASSWORD) != 0; |
| had_fingerprint = |
| (view->auth_methods() & LoginAuthUserView::AUTH_FINGERPRINT) != 0; |
| } |
| |
| int non_pin_y_start_in_screen = 0; |
| gfx::Point pin_start_in_screen; |
| bool had_pin = false; |
| bool had_password = false; |
| bool had_fingerprint = false; |
| }; |
| |
| LoginAuthUserView::TestApi::TestApi(LoginAuthUserView* view) : view_(view) {} |
| |
| LoginAuthUserView::TestApi::~TestApi() = default; |
| |
| LoginUserView* LoginAuthUserView::TestApi::user_view() const { |
| return view_->user_view_; |
| } |
| |
| LoginPasswordView* LoginAuthUserView::TestApi::password_view() const { |
| return view_->password_view_; |
| } |
| |
| LoginPinView* LoginAuthUserView::TestApi::pin_view() const { |
| return view_->pin_view_; |
| } |
| |
| views::Button* LoginAuthUserView::TestApi::online_sign_in_message() const { |
| return view_->online_sign_in_message_; |
| } |
| |
| views::View* LoginAuthUserView::TestApi::disabled_auth_message() const { |
| return view_->disabled_auth_message_; |
| } |
| |
| views::Button* LoginAuthUserView::TestApi::external_binary_auth_button() const { |
| return view_->external_binary_auth_button_; |
| } |
| |
| views::Button* LoginAuthUserView::TestApi::external_binary_enrollment_button() |
| const { |
| return view_->external_binary_enrollment_button_; |
| } |
| |
| LoginAuthUserView::Callbacks::Callbacks() = default; |
| |
| LoginAuthUserView::Callbacks::Callbacks(const Callbacks& other) = default; |
| |
| LoginAuthUserView::Callbacks::~Callbacks() = default; |
| |
| LoginAuthUserView::LoginAuthUserView(const mojom::LoginUserInfoPtr& user, |
| const Callbacks& callbacks) |
| : NonAccessibleView(kLoginAuthUserViewClassName), |
| on_auth_(callbacks.on_auth), |
| on_tap_(callbacks.on_tap) { |
| DCHECK(callbacks.on_auth); |
| DCHECK(callbacks.on_tap); |
| DCHECK(callbacks.on_remove_warning_shown); |
| DCHECK(callbacks.on_remove); |
| DCHECK(callbacks.on_easy_unlock_icon_hovered); |
| DCHECK(callbacks.on_easy_unlock_icon_tapped); |
| DCHECK_NE(user->basic_user_info->type, |
| user_manager::USER_TYPE_PUBLIC_ACCOUNT); |
| |
| // Build child views. |
| user_view_ = new LoginUserView( |
| LoginDisplayStyle::kLarge, true /*show_dropdown*/, false /*show_domain*/, |
| base::BindRepeating(&LoginAuthUserView::OnUserViewTap, |
| base::Unretained(this)), |
| callbacks.on_remove_warning_shown, callbacks.on_remove); |
| |
| password_view_ = new LoginPasswordView(); |
| password_view_->SetPaintToLayer(); // Needed for opacity animation. |
| password_view_->layer()->SetFillsBoundsOpaquely(false); |
| password_view_->UpdateForUser(user); |
| |
| pin_view_ = |
| new LoginPinView(LoginPinView::Style::kAlphanumeric, |
| base::BindRepeating(&LoginPasswordView::InsertNumber, |
| base::Unretained(password_view_)), |
| base::BindRepeating(&LoginPasswordView::Backspace, |
| base::Unretained(password_view_))); |
| DCHECK(pin_view_->layer()); |
| |
| padding_below_password_view_ = new NonAccessibleView(); |
| padding_below_password_view_->SetPreferredSize(gfx::Size( |
| kNonEmptyWidthDp, kDistanceBetweenPasswordFieldAndPinKeyboardDp)); |
| |
| // Initialization of |password_view_| is deferred because it needs the |
| // |pin_view_| pointer. |
| password_view_->Init( |
| base::Bind(&LoginAuthUserView::OnAuthSubmit, base::Unretained(this)), |
| base::Bind(&LoginPinView::OnPasswordTextChanged, |
| base::Unretained(pin_view_)), |
| callbacks.on_easy_unlock_icon_hovered, |
| callbacks.on_easy_unlock_icon_tapped); |
| |
| online_sign_in_message_ = new views::LabelButton( |
| this, base::UTF8ToUTF16(user->basic_user_info->display_name)); |
| DecorateOnlineSignInMessage(online_sign_in_message_); |
| |
| disabled_auth_message_ = new DisabledAuthMessageView(); |
| |
| fingerprint_view_ = new FingerprintView(); |
| |
| // TODO(jdufault): Implement real UI. |
| external_binary_auth_button_ = |
| views::MdTextButton::Create( |
| this, base::ASCIIToUTF16("Authenticate with external binary")) |
| .release(); |
| external_binary_enrollment_button_ = |
| views::MdTextButton::Create( |
| this, base::ASCIIToUTF16("Enroll with external binary")) |
| .release(); |
| |
| SetPaintToLayer(ui::LayerType::LAYER_NOT_DRAWN); |
| |
| // Build layout. |
| // Wrap the password view with a fill layout so that it always consumes space, |
| // ie, when the password view is hidden the wrapped view will still consume |
| // the same amount of space. This prevents the user view from shrinking. |
| auto* wrapped_password_view = new NonAccessibleView(); |
| wrapped_password_view->SetLayoutManager( |
| std::make_unique<views::FillLayout>()); |
| wrapped_password_view->AddChildView(password_view_); |
| auto* wrapped_online_sign_in_message_view = |
| login_views_utils::WrapViewForPreferredSize(online_sign_in_message_); |
| auto* wrapped_disabled_auth_message_view = |
| login_views_utils::WrapViewForPreferredSize(disabled_auth_message_); |
| auto* wrapped_user_view = |
| login_views_utils::WrapViewForPreferredSize(user_view_); |
| auto* wrapped_pin_view = |
| login_views_utils::WrapViewForPreferredSize(pin_view_); |
| auto* wrapped_fingerprint_view = |
| login_views_utils::WrapViewForPreferredSize(fingerprint_view_); |
| auto* wrapped_external_binary_view = |
| login_views_utils::WrapViewForPreferredSize(external_binary_auth_button_); |
| auto* wrapped_external_binary_enrollment_view = |
| login_views_utils::WrapViewForPreferredSize( |
| external_binary_enrollment_button_); |
| auto* wrapped_padding_below_password_view = |
| login_views_utils::WrapViewForPreferredSize(padding_below_password_view_); |
| |
| // Add views in tabbing order; they are rendered in a different order below. |
| AddChildView(wrapped_password_view); |
| AddChildView(wrapped_online_sign_in_message_view); |
| AddChildView(wrapped_disabled_auth_message_view); |
| AddChildView(wrapped_pin_view); |
| AddChildView(wrapped_fingerprint_view); |
| AddChildView(wrapped_external_binary_view); |
| AddChildView(wrapped_external_binary_enrollment_view); |
| AddChildView(wrapped_user_view); |
| AddChildView(wrapped_padding_below_password_view); |
| |
| // Use views::GridLayout instead of views::BoxLayout because views::BoxLayout |
| // lays out children according to the view->children order. |
| views::GridLayout* grid_layout = |
| SetLayoutManager(std::make_unique<views::GridLayout>(this)); |
| views::ColumnSet* column_set = grid_layout->AddColumnSet(0); |
| column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING, |
| 0 /*resize_percent*/, views::GridLayout::USE_PREF, |
| 0 /*fixed_width*/, 0 /*min_width*/); |
| auto add_view = [&](views::View* view) { |
| grid_layout->StartRow(0 /*vertical_resize*/, 0 /*column_set_id*/); |
| grid_layout->AddView(view); |
| }; |
| auto add_padding = [&](int amount) { |
| grid_layout->AddPaddingRow(0 /*vertical_resize*/, amount /*size*/); |
| }; |
| |
| // Add views in rendering order. |
| add_padding(kDistanceFromTopOfBigUserViewToUserIconDp); |
| add_view(wrapped_user_view); |
| add_padding(kDistanceBetweenUserViewAndPasswordDp); |
| add_view(wrapped_password_view); |
| add_view(wrapped_online_sign_in_message_view); |
| add_view(wrapped_disabled_auth_message_view); |
| add_view(wrapped_padding_below_password_view); |
| add_view(wrapped_pin_view); |
| add_view(wrapped_fingerprint_view); |
| add_view(wrapped_external_binary_view); |
| add_view(wrapped_external_binary_enrollment_view); |
| add_padding(kDistanceFromPinKeyboardToBigUserViewBottomDp); |
| |
| // Update authentication UI. |
| SetAuthMethods(auth_methods_, false /*can_use_pin*/); |
| user_view_->UpdateForUser(user, false /*animate*/); |
| } |
| |
| LoginAuthUserView::~LoginAuthUserView() = default; |
| |
| void LoginAuthUserView::SetAuthMethods(uint32_t auth_methods, |
| bool can_use_pin) { |
| can_use_pin_ = can_use_pin; |
| bool had_password = HasAuthMethod(AUTH_PASSWORD); |
| |
| auth_methods_ = static_cast<AuthMethods>(auth_methods); |
| bool has_password = HasAuthMethod(AUTH_PASSWORD); |
| bool has_pin = HasAuthMethod(AUTH_PIN); |
| bool has_tap = HasAuthMethod(AUTH_TAP); |
| bool force_online_sign_in = HasAuthMethod(AUTH_ONLINE_SIGN_IN); |
| bool has_fingerprint = HasAuthMethod(AUTH_FINGERPRINT); |
| bool has_external_binary = HasAuthMethod(AUTH_EXTERNAL_BINARY); |
| bool auth_disabled = HasAuthMethod(AUTH_DISABLED); |
| bool hide_auth = auth_disabled || force_online_sign_in; |
| |
| // implication: if |has_pin| is true, then |can_use_pin| must also be true |
| DCHECK(!has_pin || can_use_pin); |
| // implication: if |can_use_pin| is false, then |has_pin| must also be false |
| DCHECK(can_use_pin || !has_pin); |
| |
| online_sign_in_message_->SetVisible(force_online_sign_in); |
| disabled_auth_message_->SetVisible(auth_disabled); |
| if (auth_disabled) |
| disabled_auth_message_->RequestFocus(); |
| |
| password_view_->SetEnabled(has_password); |
| password_view_->SetEnabledOnEmptyPassword(has_tap); |
| password_view_->SetFocusEnabledForChildViews(has_password); |
| password_view_->SetVisible(!hide_auth && has_password); |
| password_view_->layer()->SetOpacity(has_password ? 1 : 0); |
| |
| if (!had_password && has_password) |
| password_view_->RequestFocus(); |
| |
| pin_view_->SetVisible(has_pin); |
| fingerprint_view_->SetVisible(has_fingerprint); |
| external_binary_auth_button_->SetVisible(has_external_binary); |
| external_binary_enrollment_button_->SetVisible(has_external_binary); |
| |
| if (has_external_binary) { |
| power_manager_client_observer_.Add(chromeos::PowerManagerClient::Get()); |
| } |
| |
| int padding_view_height = kDistanceBetweenPasswordFieldAndPinKeyboardDp; |
| if (has_fingerprint && !has_pin) { |
| padding_view_height = kDistanceBetweenPasswordFieldAndFingerprintViewDp; |
| } |
| padding_below_password_view_->SetPreferredSize( |
| gfx::Size(kNonEmptyWidthDp, padding_view_height)); |
| |
| // Note: if both |has_tap| and |has_pin| are true, prefer tap placeholder. |
| if (has_tap) { |
| password_view_->SetPlaceholderText( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_POD_PASSWORD_TAP_PLACEHOLDER)); |
| } else if (has_pin) { |
| password_view_->SetPlaceholderText( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_POD_PASSWORD_PIN_PLACEHOLDER)); |
| } else { |
| password_view_->SetPlaceholderText( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_POD_PASSWORD_PLACEHOLDER)); |
| } |
| |
| // Only the active auth user view has a password displayed. If that is the |
| // case, then render the user view as if it was always focused, since clicking |
| // on it will not do anything (such as swapping users). |
| user_view_->SetForceOpaque(has_password || hide_auth); |
| user_view_->SetTapEnabled(!has_password); |
| // Tapping the user view will trigger the online sign-in flow when |
| // |force_online_sign_in| is true. |
| if (force_online_sign_in) |
| user_view_->RequestFocus(); |
| |
| PreferredSizeChanged(); |
| } |
| |
| void LoginAuthUserView::SetEasyUnlockIcon( |
| mojom::EasyUnlockIconId id, |
| const base::string16& accessibility_label) { |
| password_view_->SetEasyUnlockIcon(id, accessibility_label); |
| } |
| |
| void LoginAuthUserView::CaptureStateForAnimationPreLayout() { |
| auto stop_animation = [](views::View* view) { |
| if (view->layer()->GetAnimator()->is_animating()) |
| view->layer()->GetAnimator()->StopAnimating(); |
| }; |
| |
| // Stop any running animation scheduled in ApplyAnimationPostLayout. |
| stop_animation(this); |
| stop_animation(password_view_); |
| stop_animation(pin_view_); |
| stop_animation(fingerprint_view_); |
| |
| DCHECK(!cached_animation_state_); |
| cached_animation_state_ = std::make_unique<AnimationState>(this); |
| } |
| |
| void LoginAuthUserView::ApplyAnimationPostLayout() { |
| DCHECK(cached_animation_state_); |
| |
| bool has_password = (auth_methods() & AUTH_PASSWORD) != 0; |
| bool has_pin = (auth_methods() & AUTH_PIN) != 0; |
| bool has_fingerprint = (auth_methods() & AUTH_FINGERPRINT) != 0; |
| |
| //////// |
| // Animate the user info (ie, icon, name) up or down the screen. |
| |
| int non_pin_y_end_in_screen = GetBoundsInScreen().y(); |
| |
| // Transform the layer so the user view renders where it used to be. This |
| // requires a y offset. |
| // Note: Doing this animation via ui::ScopedLayerAnimationSettings works, but |
| // it seems that the timing gets slightly out of sync with the PIN animation. |
| auto move_to_center = std::make_unique<ui::InterpolatedTranslation>( |
| gfx::PointF(0, cached_animation_state_->non_pin_y_start_in_screen - |
| non_pin_y_end_in_screen), |
| gfx::PointF()); |
| auto transition = |
| ui::LayerAnimationElement::CreateInterpolatedTransformElement( |
| std::move(move_to_center), |
| base::TimeDelta::FromMilliseconds( |
| login_constants::kChangeUserAnimationDurationMs)); |
| transition->set_tween_type(gfx::Tween::Type::FAST_OUT_SLOW_IN); |
| layer()->GetAnimator()->StartAnimation( |
| new ui::LayerAnimationSequence(std::move(transition))); |
| |
| //////// |
| // Fade the password view if it is being hidden or shown. |
| |
| if (cached_animation_state_->had_password != has_password) { |
| float opacity_start = 0, opacity_end = 1; |
| if (!has_password) |
| std::swap(opacity_start, opacity_end); |
| |
| if (cached_animation_state_->had_password) |
| password_view_->SetVisible(true); |
| |
| password_view_->layer()->SetOpacity(opacity_start); |
| |
| { |
| ui::ScopedLayerAnimationSettings settings( |
| password_view_->layer()->GetAnimator()); |
| settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( |
| login_constants::kChangeUserAnimationDurationMs)); |
| settings.SetTweenType(gfx::Tween::Type::FAST_OUT_SLOW_IN); |
| if (cached_animation_state_->had_password && !has_password) { |
| settings.AddObserver( |
| new ClearPasswordAndHideAnimationObserver(password_view_)); |
| } |
| |
| password_view_->layer()->SetOpacity(opacity_end); |
| } |
| } |
| |
| //////// |
| // Grow/shrink the PIN keyboard if it is being hidden or shown. |
| |
| if (cached_animation_state_->had_pin != has_pin) { |
| if (!has_pin) { |
| gfx::Point pin_end_in_screen = pin_view_->GetBoundsInScreen().origin(); |
| gfx::Rect pin_bounds = pin_view_->bounds(); |
| pin_bounds.set_x(cached_animation_state_->pin_start_in_screen.x() - |
| pin_end_in_screen.x()); |
| pin_bounds.set_y(cached_animation_state_->pin_start_in_screen.y() - |
| pin_end_in_screen.y()); |
| |
| // Since PIN is disabled, the previous Layout() hid the PIN keyboard. |
| // We need to redisplay it where it used to be. |
| pin_view_->SetVisible(true); |
| pin_view_->SetBoundsRect(pin_bounds); |
| } |
| |
| auto transition = std::make_unique<PinKeyboardAnimation>( |
| has_pin /*grow*/, pin_view_->height(), |
| base::TimeDelta::FromMilliseconds( |
| login_constants::kChangeUserAnimationDurationMs), |
| gfx::Tween::FAST_OUT_SLOW_IN); |
| auto* sequence = new ui::LayerAnimationSequence(std::move(transition)); |
| |
| // Hide the PIN keyboard after animation if needed. |
| if (!has_pin) { |
| auto* observer = BuildObserverToHideView(pin_view_); |
| sequence->AddObserver(observer); |
| observer->SetActive(); |
| } |
| |
| pin_view_->layer()->GetAnimator()->ScheduleAnimation(sequence); |
| } |
| |
| //////// |
| // Fade the fingerprint view if it is being hidden or shown. |
| |
| if (cached_animation_state_->had_fingerprint != has_fingerprint) { |
| float opacity_start = 0, opacity_end = 1; |
| if (!has_fingerprint) |
| std::swap(opacity_start, opacity_end); |
| |
| fingerprint_view_->layer()->SetOpacity(opacity_start); |
| |
| { |
| ui::ScopedLayerAnimationSettings settings( |
| fingerprint_view_->layer()->GetAnimator()); |
| settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( |
| login_constants::kChangeUserAnimationDurationMs)); |
| settings.SetTweenType(gfx::Tween::Type::FAST_OUT_SLOW_IN); |
| fingerprint_view_->layer()->SetOpacity(opacity_end); |
| } |
| } |
| |
| cached_animation_state_.reset(); |
| } |
| |
| void LoginAuthUserView::UpdateForUser(const mojom::LoginUserInfoPtr& user) { |
| user_view_->UpdateForUser(user, true /*animate*/); |
| password_view_->UpdateForUser(user); |
| password_view_->Clear(); |
| online_sign_in_message_->SetText( |
| base::UTF8ToUTF16(user->basic_user_info->display_name)); |
| } |
| |
| void LoginAuthUserView::SetFingerprintState(mojom::FingerprintState state) { |
| fingerprint_view_->SetState(state); |
| } |
| |
| void LoginAuthUserView::NotifyFingerprintAuthResult(bool success) { |
| fingerprint_view_->NotifyFingerprintAuthResult(success); |
| } |
| |
| void LoginAuthUserView::SetAuthDisabledMessage( |
| const ash::mojom::AuthDisabledDataPtr& auth_disabled_data) { |
| disabled_auth_message_->SetAuthDisabledMessage(auth_disabled_data); |
| Layout(); |
| } |
| |
| const mojom::LoginUserInfoPtr& LoginAuthUserView::current_user() const { |
| return user_view_->current_user(); |
| } |
| |
| gfx::Size LoginAuthUserView::CalculatePreferredSize() const { |
| gfx::Size size = views::View::CalculatePreferredSize(); |
| // Make sure we are at least as big as the user view. If we do not do this the |
| // view will be below minimum size when no auth methods are displayed. |
| size.SetToMax(user_view_->GetPreferredSize()); |
| return size; |
| } |
| |
| void LoginAuthUserView::RequestFocus() { |
| password_view_->RequestFocus(); |
| } |
| |
| void LoginAuthUserView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| DCHECK(sender == online_sign_in_message_ || |
| sender == external_binary_auth_button_); |
| if (sender == online_sign_in_message_) { |
| OnOnlineSignInMessageTap(); |
| } else if (sender == external_binary_auth_button_) { |
| AttemptAuthenticateWithExternalBinary(); |
| } else if (sender == external_binary_enrollment_button_) { |
| password_view_->SetReadOnly(true); |
| external_binary_auth_button_->SetEnabled(false); |
| external_binary_enrollment_button_->SetEnabled(false); |
| Shell::Get()->login_screen_controller()->EnrollUserWithExternalBinary( |
| base::BindOnce(&LoginAuthUserView::OnEnrollmentComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void LoginAuthUserView::LidEventReceived( |
| chromeos::PowerManagerClient::LidState state, |
| const base::TimeTicks& timestamp) { |
| if (state == chromeos::PowerManagerClient::LidState::OPEN) |
| AttemptAuthenticateWithExternalBinary(); |
| } |
| |
| void LoginAuthUserView::OnAuthSubmit(const base::string16& password) { |
| // Pressing enter when the password field is empty and tap-to-unlock is |
| // enabled should attempt unlock. |
| if (HasAuthMethod(AUTH_TAP) && password.empty()) { |
| Shell::Get()->login_screen_controller()->AuthenticateUserWithEasyUnlock( |
| current_user()->basic_user_info->account_id); |
| return; |
| } |
| |
| password_view_->SetReadOnly(true); |
| Shell::Get()->login_screen_controller()->AuthenticateUserWithPasswordOrPin( |
| current_user()->basic_user_info->account_id, base::UTF16ToUTF8(password), |
| can_use_pin_, |
| base::BindOnce(&LoginAuthUserView::OnAuthComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void LoginAuthUserView::OnAuthComplete(base::Optional<bool> auth_success) { |
| // Clear the password only if auth fails. Make sure to keep the password view |
| // disabled even if auth succeededs, as if the user submits a password while |
| // animating the next lock screen will not work as expected. See |
| // https://crbug.com/808486. |
| if (!auth_success.has_value() || !auth_success.value()) { |
| password_view_->Clear(); |
| password_view_->SetReadOnly(false); |
| external_binary_auth_button_->SetEnabled(true); |
| external_binary_enrollment_button_->SetEnabled(true); |
| } |
| |
| on_auth_.Run(auth_success.value()); |
| } |
| |
| void LoginAuthUserView::OnEnrollmentComplete( |
| base::Optional<bool> enrollment_success) { |
| password_view_->SetReadOnly(false); |
| external_binary_auth_button_->SetEnabled(true); |
| external_binary_enrollment_button_->SetEnabled(true); |
| |
| std::string result_message; |
| if (!enrollment_success.has_value()) { |
| result_message = "Enrollment attempt failed to received response."; |
| } else { |
| result_message = enrollment_success.value() ? "Enrollment successful." |
| : "Enrollment failed."; |
| } |
| |
| ToastData toast_data("EnrollmentToast", base::ASCIIToUTF16(result_message), |
| 2000, base::nullopt, true /*visible_on_lock_screen*/); |
| Shell::Get()->toast_manager()->Show(toast_data); |
| } |
| |
| void LoginAuthUserView::OnUserViewTap() { |
| if (HasAuthMethod(AUTH_TAP)) { |
| Shell::Get()->login_screen_controller()->AuthenticateUserWithEasyUnlock( |
| current_user()->basic_user_info->account_id); |
| } else if (HasAuthMethod(AUTH_ONLINE_SIGN_IN)) { |
| // Tapping anywhere in the user view is the same with tapping the message. |
| OnOnlineSignInMessageTap(); |
| } else { |
| on_tap_.Run(); |
| } |
| } |
| |
| void LoginAuthUserView::OnOnlineSignInMessageTap() { |
| Shell::Get()->login_screen_controller()->ShowGaiaSignin( |
| true /*can_close*/, current_user()->basic_user_info->account_id); |
| } |
| |
| bool LoginAuthUserView::HasAuthMethod(AuthMethods auth_method) const { |
| return (auth_methods_ & auth_method) != 0; |
| } |
| |
| void LoginAuthUserView::AttemptAuthenticateWithExternalBinary() { |
| password_view_->SetReadOnly(true); |
| external_binary_auth_button_->SetEnabled(false); |
| external_binary_enrollment_button_->SetEnabled(false); |
| Shell::Get()->login_screen_controller()->AuthenticateUserWithExternalBinary( |
| current_user()->basic_user_info->account_id, |
| base::BindOnce(&LoginAuthUserView::OnAuthComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| } // namespace ash |