| // 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/lock_contents_view.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/accelerators/accelerator_controller.h" |
| #include "ash/detachable_base/detachable_base_pairing_status.h" |
| #include "ash/focus_cycler.h" |
| #include "ash/ime/ime_controller.h" |
| #include "ash/login/login_screen_controller.h" |
| #include "ash/login/ui/lock_screen.h" |
| #include "ash/login/ui/login_auth_user_view.h" |
| #include "ash/login/ui/login_big_user_view.h" |
| #include "ash/login/ui/login_bubble.h" |
| #include "ash/login/ui/login_detachable_base_model.h" |
| #include "ash/login/ui/login_expanded_public_account_view.h" |
| #include "ash/login/ui/login_public_account_user_view.h" |
| #include "ash/login/ui/login_user_view.h" |
| #include "ash/login/ui/non_accessible_view.h" |
| #include "ash/login/ui/note_action_launch_button.h" |
| #include "ash/login/ui/scrollable_users_list_view.h" |
| #include "ash/login/ui/views_utils.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_widget.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/system/status_area_widget_delegate.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "components/user_manager/user_type.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/display/display.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/managed_display_info.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/vector2d.h" |
| #include "ui/views/accessibility/ax_aura_obj_cache.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/controls/styled_label.h" |
| #include "ui/views/focus/focus_search.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/view.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Any non-zero value used for separator height. Makes debugging easier; this |
| // should not affect visual appearance. |
| constexpr int kNonEmptyHeightDp = 30; |
| |
| // Horizontal distance between two users in the low density layout. |
| constexpr int kLowDensityDistanceBetweenUsersInLandscapeDp = 118; |
| constexpr int kLowDensityDistanceBetweenUsersInPortraitDp = 32; |
| |
| // Margin left of the auth user in the medium density layout. |
| constexpr int kMediumDensityMarginLeftOfAuthUserLandscapeDp = 98; |
| constexpr int kMediumDensityMarginLeftOfAuthUserPortraitDp = 0; |
| |
| // Horizontal distance between the auth user and the medium density user row. |
| constexpr int kMediumDensityDistanceBetweenAuthUserAndUsersLandscapeDp = 220; |
| constexpr int kMediumDensityDistanceBetweenAuthUserAndUsersPortraitDp = 84; |
| |
| constexpr char kLockContentsViewName[] = "LockContentsView"; |
| |
| // A view which stores two preferred sizes. The embedder can control which one |
| // is used. |
| class MultiSizedView : public views::View { |
| public: |
| MultiSizedView(const gfx::Size& a, const gfx::Size& b) : a_(a), b_(b) {} |
| ~MultiSizedView() override = default; |
| |
| void SwapPreferredSizeTo(bool use_a) { |
| if (use_a) |
| SetPreferredSize(a_); |
| else |
| SetPreferredSize(b_); |
| } |
| |
| private: |
| gfx::Size a_; |
| gfx::Size b_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MultiSizedView); |
| }; |
| |
| // Returns the first or last focusable child of |root|. If |reverse| is false, |
| // this returns the first focusable child. If |reverse| is true, this returns |
| // the last focusable child. |
| views::View* FindFirstOrLastFocusableChild(views::View* root, bool reverse) { |
| views::FocusSearch search(root, reverse /*cycle*/, |
| false /*accessibility_mode*/); |
| views::FocusTraversable* dummy_focus_traversable; |
| views::View* dummy_focus_traversable_view; |
| return search.FindNextFocusableView( |
| root, |
| reverse ? views::FocusSearch::SearchDirection::kBackwards |
| : views::FocusSearch::SearchDirection::kForwards, |
| views::FocusSearch::TraversalDirection::kDown, |
| views::FocusSearch::StartingViewPolicy::kSkipStartingView, |
| views::FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog, |
| &dummy_focus_traversable, &dummy_focus_traversable_view); |
| } |
| |
| // Make a section of the text bold. |
| // |label|: The label to apply mixed styles. |
| // |text|: The message to display. |
| // |bold_start|: The position in |text| to start bolding. |
| // |bold_length|: The length of bold text. |
| void MakeSectionBold(views::StyledLabel* label, |
| const base::string16& text, |
| const base::Optional<int>& bold_start, |
| int bold_length) { |
| auto create_style = [&](bool is_bold) { |
| views::StyledLabel::RangeStyleInfo style; |
| if (is_bold) { |
| style.custom_font = label->GetDefaultFontList().Derive( |
| 0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::BOLD); |
| } |
| style.override_color = SK_ColorWHITE; |
| return style; |
| }; |
| |
| auto add_style = [&](const views::StyledLabel::RangeStyleInfo& style, |
| int start, int end) { |
| if (start >= end) |
| return; |
| |
| label->AddStyleRange(gfx::Range(start, end), style); |
| }; |
| |
| views::StyledLabel::RangeStyleInfo regular_style = |
| create_style(false /*is_bold*/); |
| views::StyledLabel::RangeStyleInfo bold_style = |
| create_style(true /*is_bold*/); |
| if (!bold_start || bold_length == 0) { |
| add_style(regular_style, 0, text.length()); |
| return; |
| } |
| |
| add_style(regular_style, 0, *bold_start - 1); |
| add_style(bold_style, *bold_start, *bold_start + bold_length); |
| add_style(regular_style, *bold_start + bold_length + 1, text.length()); |
| } |
| |
| // Helper function to create a label for the dev channel info view. |
| views::Label* CreateInfoLabel() { |
| views::Label* label = new views::Label(); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetEnabledColor(SK_ColorWHITE); |
| label->SetFontList(views::Label::GetDefaultFontList().Derive( |
| -1, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL)); |
| label->SetSubpixelRenderingEnabled(false); |
| |
| return label; |
| } |
| |
| keyboard::KeyboardController* GetKeyboardControllerForWidget( |
| const views::Widget* widget) { |
| auto* keyboard_controller = keyboard::KeyboardController::Get(); |
| if (!keyboard_controller->enabled()) |
| return nullptr; |
| |
| aura::Window* keyboard_window = keyboard_controller->GetRootWindow(); |
| aura::Window* this_window = widget->GetNativeWindow()->GetRootWindow(); |
| return keyboard_window == this_window ? keyboard_controller : nullptr; |
| } |
| |
| bool IsPublicAccountUser(const mojom::LoginUserInfoPtr& user) { |
| return user->basic_user_info->type == user_manager::USER_TYPE_PUBLIC_ACCOUNT; |
| } |
| |
| } // namespace |
| |
| LockContentsView::TestApi::TestApi(LockContentsView* view) : view_(view) {} |
| |
| LockContentsView::TestApi::~TestApi() = default; |
| |
| LoginBigUserView* LockContentsView::TestApi::primary_big_view() const { |
| return view_->primary_big_view_; |
| } |
| |
| LoginBigUserView* LockContentsView::TestApi::opt_secondary_big_view() const { |
| return view_->opt_secondary_big_view_; |
| } |
| |
| ScrollableUsersListView* LockContentsView::TestApi::users_list() const { |
| return view_->users_list_; |
| } |
| |
| views::View* LockContentsView::TestApi::note_action() const { |
| return view_->note_action_; |
| } |
| |
| LoginBubble* LockContentsView::TestApi::tooltip_bubble() const { |
| return view_->tooltip_bubble_.get(); |
| } |
| |
| LoginBubble* LockContentsView::TestApi::auth_error_bubble() const { |
| return view_->auth_error_bubble_.get(); |
| } |
| |
| LoginBubble* LockContentsView::TestApi::detachable_base_error_bubble() const { |
| return view_->detachable_base_error_bubble_.get(); |
| } |
| |
| views::View* LockContentsView::TestApi::dev_channel_info() const { |
| return view_->dev_channel_info_; |
| } |
| |
| LoginExpandedPublicAccountView* LockContentsView::TestApi::expanded_view() |
| const { |
| return view_->expanded_view_; |
| } |
| |
| views::View* LockContentsView::TestApi::main_view() const { |
| return view_->main_view_; |
| } |
| |
| LockContentsView::UserState::UserState(const mojom::LoginUserInfoPtr& user_info) |
| : account_id(user_info->basic_user_info->account_id) { |
| fingerprint_state = user_info->allow_fingerprint_unlock |
| ? mojom::FingerprintUnlockState::AVAILABLE |
| : mojom::FingerprintUnlockState::UNAVAILABLE; |
| if (user_info->auth_type == proximity_auth::mojom::AuthType::ONLINE_SIGN_IN) |
| force_online_sign_in = true; |
| } |
| |
| LockContentsView::UserState::UserState(UserState&&) = default; |
| |
| LockContentsView::UserState::~UserState() = default; |
| |
| // static |
| const int LockContentsView::kLoginAttemptsBeforeGaiaDialog = 4; |
| |
| LockContentsView::LockContentsView( |
| mojom::TrayActionState initial_note_action_state, |
| LockScreen::ScreenType screen_type, |
| LoginDataDispatcher* data_dispatcher, |
| std::unique_ptr<LoginDetachableBaseModel> detachable_base_model) |
| : NonAccessibleView(kLockContentsViewName), |
| screen_type_(screen_type), |
| data_dispatcher_(data_dispatcher), |
| detachable_base_model_(std::move(detachable_base_model)), |
| display_observer_(this), |
| session_observer_(this) { |
| data_dispatcher_->AddObserver(this); |
| display_observer_.Add(display::Screen::GetScreen()); |
| Shell::Get()->login_screen_controller()->AddObserver(this); |
| Shell::Get()->system_tray_notifier()->AddSystemTrayFocusObserver(this); |
| keyboard::KeyboardController::Get()->AddObserver(this); |
| auth_error_bubble_ = std::make_unique<LoginBubble>(); |
| detachable_base_error_bubble_ = std::make_unique<LoginBubble>(); |
| tooltip_bubble_ = std::make_unique<LoginBubble>(); |
| |
| // We reuse the focusable state on this view as a signal that focus should |
| // switch to the system tray. LockContentsView should otherwise not be |
| // focusable. |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| |
| main_view_ = new NonAccessibleView(); |
| AddChildView(main_view_); |
| |
| // The top header view. |
| top_header_ = new views::View(); |
| auto top_header_layout = |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal); |
| top_header_layout->set_main_axis_alignment( |
| views::BoxLayout::MAIN_AXIS_ALIGNMENT_END); |
| top_header_->SetLayoutManager(std::move(top_header_layout)); |
| AddChildView(top_header_); |
| |
| dev_channel_info_ = new views::View(); |
| auto dev_channel_info_layout = std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kVertical, gfx::Insets(5, 8)); |
| dev_channel_info_layout->set_cross_axis_alignment( |
| views::BoxLayout::CROSS_AXIS_ALIGNMENT_END); |
| dev_channel_info_->SetLayoutManager(std::move(dev_channel_info_layout)); |
| dev_channel_info_->SetVisible(false); |
| top_header_->AddChildView(dev_channel_info_); |
| |
| note_action_ = new NoteActionLaunchButton(initial_note_action_state); |
| top_header_->AddChildView(note_action_); |
| |
| // Public Session expanded view. |
| expanded_view_ = new LoginExpandedPublicAccountView( |
| base::BindRepeating(&LockContentsView::SetDisplayStyle, |
| base::Unretained(this), DisplayStyle::kAll)); |
| expanded_view_->SetVisible(false); |
| AddChildView(expanded_view_); |
| |
| OnLockScreenNoteStateChanged(initial_note_action_state); |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver( |
| this); |
| RegisterAccelerators(); |
| } |
| |
| LockContentsView::~LockContentsView() { |
| Shell::Get()->accelerator_controller()->UnregisterAll(this); |
| data_dispatcher_->RemoveObserver(this); |
| Shell::Get()->login_screen_controller()->RemoveObserver(this); |
| keyboard::KeyboardController::Get()->RemoveObserver(this); |
| Shell::Get()->system_tray_notifier()->RemoveSystemTrayFocusObserver(this); |
| |
| if (unlock_attempt_ > 0) { |
| // Times a password was incorrectly entered until user gives up (sign out |
| // current session or shutdown the device). For a successful unlock, |
| // unlock_attempt_ should already be reset by OnLockStateChanged. |
| Shell::Get()->metrics()->login_metrics_recorder()->RecordNumLoginAttempts( |
| unlock_attempt_, false /*success*/); |
| } |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver( |
| this); |
| } |
| |
| void LockContentsView::FocusNextUser() { |
| if (login_views_utils::HasFocusInAnyChildView(primary_big_view_)) { |
| if (opt_secondary_big_view_) { |
| SwapActiveAuthBetweenPrimaryAndSecondary(false /*is_primary*/); |
| opt_secondary_big_view_->RequestFocus(); |
| } else if (users_list_) { |
| users_list_->user_view_at(0)->RequestFocus(); |
| } |
| return; |
| } |
| |
| if (opt_secondary_big_view_ && |
| login_views_utils::HasFocusInAnyChildView(opt_secondary_big_view_)) { |
| SwapActiveAuthBetweenPrimaryAndSecondary(true /*is_primary*/); |
| primary_big_view_->RequestFocus(); |
| return; |
| } |
| |
| for (int i = 0; i < users_list_->user_count(); ++i) { |
| LoginUserView* user_view = users_list_->user_view_at(i); |
| if (!login_views_utils::HasFocusInAnyChildView(user_view)) |
| continue; |
| |
| if (i == users_list_->user_count() - 1) { |
| SwapActiveAuthBetweenPrimaryAndSecondary(true /*is_primary*/); |
| primary_big_view_->RequestFocus(); |
| return; |
| } |
| |
| user_view->GetNextFocusableView()->RequestFocus(); |
| return; |
| } |
| } |
| |
| void LockContentsView::FocusPreviousUser() { |
| if (login_views_utils::HasFocusInAnyChildView(primary_big_view_)) { |
| if (users_list_) { |
| users_list_->user_view_at(users_list_->user_count() - 1)->RequestFocus(); |
| } else if (opt_secondary_big_view_) { |
| SwapActiveAuthBetweenPrimaryAndSecondary(false /*is_primary*/); |
| opt_secondary_big_view_->RequestFocus(); |
| } |
| return; |
| } |
| |
| if (opt_secondary_big_view_ && |
| login_views_utils::HasFocusInAnyChildView(opt_secondary_big_view_)) { |
| SwapActiveAuthBetweenPrimaryAndSecondary(true /*is_primary*/); |
| primary_big_view_->RequestFocus(); |
| return; |
| } |
| |
| for (int i = 0; i < users_list_->user_count(); ++i) { |
| LoginUserView* user_view = users_list_->user_view_at(i); |
| if (!login_views_utils::HasFocusInAnyChildView(user_view)) |
| continue; |
| |
| if (i == 0) { |
| SwapActiveAuthBetweenPrimaryAndSecondary(true /*is_primary*/); |
| primary_big_view_->RequestFocus(); |
| return; |
| } |
| |
| user_view->GetPreviousFocusableView()->RequestFocus(); |
| return; |
| } |
| } |
| |
| void LockContentsView::Layout() { |
| View::Layout(); |
| LayoutTopHeader(); |
| LayoutPublicSessionView(); |
| |
| if (users_list_) |
| users_list_->Layout(); |
| } |
| |
| void LockContentsView::AddedToWidget() { |
| DoLayout(); |
| |
| // Focus the primary user when showing the UI. This will focus the password. |
| if (primary_big_view_) |
| primary_big_view_->RequestFocus(); |
| } |
| |
| void LockContentsView::OnFocus() { |
| // If LockContentsView somehow gains focus (ie, a test, but it should not |
| // under typical circumstances), immediately forward the focus to the |
| // primary_big_view_ since LockContentsView has no real focusable content by |
| // itself. |
| if (primary_big_view_) |
| primary_big_view_->RequestFocus(); |
| } |
| |
| void LockContentsView::AboutToRequestFocusFromTabTraversal(bool reverse) { |
| // The LockContentsView itself doesn't have anything to focus. If it gets |
| // focused we should change the currently focused widget (ie, to the shelf or |
| // status area, or lock screen apps, if they are active). |
| if (reverse && lock_screen_apps_active_) { |
| Shell::Get()->login_screen_controller()->FocusLockScreenApps(reverse); |
| return; |
| } |
| |
| FocusNextWidget(reverse); |
| } |
| |
| void LockContentsView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow()); |
| ShelfWidget* shelf_widget = shelf->shelf_widget(); |
| int next_id = views::AXAuraObjCache::GetInstance()->GetID(shelf_widget); |
| node_data->AddIntAttribute(ax::mojom::IntAttribute::kNextFocusId, next_id); |
| |
| int previous_id = |
| views::AXAuraObjCache::GetInstance()->GetID(shelf->GetStatusAreaWidget()); |
| node_data->AddIntAttribute(ax::mojom::IntAttribute::kPreviousFocusId, |
| previous_id); |
| node_data->SetNameExplicitlyEmpty(); |
| } |
| |
| bool LockContentsView::AcceleratorPressed(const ui::Accelerator& accelerator) { |
| auto entry = accel_map_.find(accelerator); |
| if (entry == accel_map_.end()) |
| return false; |
| |
| PerformAction(entry->second); |
| return true; |
| } |
| |
| void LockContentsView::OnUsersChanged( |
| const std::vector<mojom::LoginUserInfoPtr>& users) { |
| // The debug view will potentially call this method many times. Make sure to |
| // invalidate any child references. |
| main_view_->RemoveAllChildViews(true /*delete_children*/); |
| opt_secondary_big_view_ = nullptr; |
| users_list_ = nullptr; |
| rotation_actions_.clear(); |
| users_.clear(); |
| |
| // If there are no users, show gaia signin if login, otherwise crash. |
| if (users.empty()) { |
| LOG_IF(FATAL, screen_type_ != LockScreen::ScreenType::kLogin) |
| << "Empty user list received"; |
| Shell::Get()->login_screen_controller()->ShowGaiaSignin( |
| false /*can_close*/, base::nullopt /*prefilled_account*/); |
| return; |
| } |
| |
| // Build user state list. |
| for (const mojom::LoginUserInfoPtr& user : users) |
| users_.push_back(UserState(user)); |
| |
| auto box_layout = |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal); |
| main_layout_ = box_layout.get(); |
| main_layout_->set_main_axis_alignment( |
| views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER); |
| main_layout_->set_cross_axis_alignment( |
| views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); |
| main_view_->SetLayoutManager(std::move(box_layout)); |
| |
| // Add big user. |
| primary_big_view_ = AllocateLoginBigUserView(users[0], true /*is_primary*/); |
| main_view_->AddChildView(primary_big_view_); |
| |
| // Build layout for additional users. |
| if (users.size() == 2) |
| CreateLowDensityLayout(users); |
| else if (users.size() >= 3 && users.size() <= 6) |
| CreateMediumDensityLayout(users); |
| else if (users.size() >= 7) |
| CreateHighDensityLayout(users); |
| |
| LayoutAuth(primary_big_view_, opt_secondary_big_view_, false /*animate*/); |
| |
| // Big user may be the same if we already built lock screen. |
| OnBigUserChanged(); |
| |
| // Force layout. |
| PreferredSizeChanged(); |
| Layout(); |
| } |
| |
| void LockContentsView::OnPinEnabledForUserChanged(const AccountId& user, |
| bool enabled) { |
| LockContentsView::UserState* state = FindStateForUser(user); |
| if (!state) { |
| LOG(ERROR) << "Unable to find user when changing PIN state to " << enabled; |
| return; |
| } |
| |
| state->show_pin = enabled; |
| |
| LoginBigUserView* big_user = |
| TryToFindBigUser(user, true /*require_auth_active*/); |
| if (big_user && big_user->auth_user()) |
| LayoutAuth(big_user, nullptr /*opt_to_hide*/, true /*animate*/); |
| } |
| |
| void LockContentsView::OnAuthEnabledForUserChanged( |
| const AccountId& user, |
| bool enabled, |
| const base::Optional<base::Time>& auth_reenabled_time) { |
| LockContentsView::UserState* state = FindStateForUser(user); |
| if (!state) { |
| LOG(ERROR) << "Unable to find user when changing auth enabled state to " |
| << enabled; |
| return; |
| } |
| |
| DCHECK(enabled || auth_reenabled_time); |
| state->disable_auth = !enabled; |
| // TODO(crbug.com/845287): Reenable lock screen note when auth is reenabled. |
| if (state->disable_auth) |
| DisableLockScreenNote(); |
| |
| LoginBigUserView* big_user = |
| TryToFindBigUser(user, true /*require_auth_active*/); |
| if (big_user && big_user->auth_user()) { |
| LayoutAuth(big_user, nullptr /*opt_to_hide*/, true /*animate*/); |
| if (auth_reenabled_time) |
| big_user->auth_user()->SetAuthReenabledTime(auth_reenabled_time.value()); |
| } |
| } |
| |
| void LockContentsView::OnClickToUnlockEnabledForUserChanged( |
| const AccountId& user, |
| bool enabled) { |
| LockContentsView::UserState* state = FindStateForUser(user); |
| if (!state) { |
| LOG(ERROR) << "Unable to find user enabling click to auth"; |
| return; |
| } |
| state->enable_tap_auth = enabled; |
| |
| LoginBigUserView* big_user = |
| TryToFindBigUser(user, true /*require_auth_active*/); |
| if (big_user && big_user->auth_user()) |
| LayoutAuth(big_user, nullptr /*opt_to_hide*/, true /*animate*/); |
| } |
| |
| void LockContentsView::OnForceOnlineSignInForUser(const AccountId& user) { |
| LockContentsView::UserState* state = FindStateForUser(user); |
| if (!state) { |
| LOG(ERROR) << "Unable to find user forcing online sign in"; |
| return; |
| } |
| state->force_online_sign_in = true; |
| |
| LoginBigUserView* big_user = |
| TryToFindBigUser(user, true /*require_auth_active*/); |
| if (big_user && big_user->auth_user()) |
| LayoutAuth(big_user, nullptr /*opt_to_hide*/, true /*animate*/); |
| } |
| |
| void LockContentsView::OnShowEasyUnlockIcon( |
| const AccountId& user, |
| const mojom::EasyUnlockIconOptionsPtr& icon) { |
| UserState* state = FindStateForUser(user); |
| if (!state) |
| return; |
| |
| state->easy_unlock_state = icon->Clone(); |
| UpdateEasyUnlockIconForUser(user); |
| |
| // Show tooltip only if the user is actively showing auth. |
| LoginBigUserView* big_user = |
| TryToFindBigUser(user, true /*require_auth_active*/); |
| if (!big_user || !big_user->auth_user()) |
| return; |
| |
| tooltip_bubble_->Close(); |
| if (icon->autoshow_tooltip) { |
| tooltip_bubble_->ShowTooltip( |
| icon->tooltip, big_user->auth_user()->password_view() /*anchor_view*/); |
| } |
| } |
| |
| void LockContentsView::OnLockScreenNoteStateChanged( |
| mojom::TrayActionState state) { |
| if (disable_lock_screen_note_) |
| state = mojom::TrayActionState::kNotAvailable; |
| |
| bool old_lock_screen_apps_active = lock_screen_apps_active_; |
| lock_screen_apps_active_ = state == mojom::TrayActionState::kActive; |
| note_action_->UpdateVisibility(state); |
| LayoutTopHeader(); |
| |
| // If lock screen apps just got deactivated - request focus for primary auth, |
| // which should focus the password field. |
| if (old_lock_screen_apps_active && !lock_screen_apps_active_ && |
| primary_big_view_) { |
| primary_big_view_->RequestFocus(); |
| } |
| } |
| |
| void LockContentsView::OnDevChannelInfoChanged( |
| const std::string& os_version_label_text, |
| const std::string& enterprise_info_text, |
| const std::string& bluetooth_name) { |
| DCHECK(!os_version_label_text.empty() || !enterprise_info_text.empty() || |
| !bluetooth_name.empty()); |
| |
| if (!dev_channel_info_->visible()) { |
| // Initialize the dev channel info view. |
| dev_channel_info_->SetVisible(true); |
| for (int i = 0; i < 3; ++i) |
| dev_channel_info_->AddChildView(CreateInfoLabel()); |
| } |
| |
| views::Label* version_label = |
| static_cast<views::Label*>(dev_channel_info_->child_at(0)); |
| version_label->SetVisible(!os_version_label_text.empty()); |
| version_label->SetText(base::UTF8ToUTF16(os_version_label_text)); |
| |
| views::Label* enterprise_label = |
| static_cast<views::Label*>(dev_channel_info_->child_at(1)); |
| enterprise_label->SetVisible(!enterprise_info_text.empty()); |
| enterprise_label->SetText(base::UTF8ToUTF16(enterprise_info_text)); |
| |
| views::Label* bluetooth_label = |
| static_cast<views::Label*>(dev_channel_info_->child_at(2)); |
| bluetooth_label->SetVisible(!bluetooth_name.empty()); |
| bluetooth_label->SetText(base::UTF8ToUTF16(bluetooth_name)); |
| |
| LayoutTopHeader(); |
| } |
| |
| void LockContentsView::OnPublicSessionDisplayNameChanged( |
| const AccountId& account_id, |
| const std::string& display_name) { |
| LoginUserView* user_view = TryToFindUserView(account_id); |
| if (!user_view || !IsPublicAccountUser(user_view->current_user())) |
| return; |
| |
| mojom::LoginUserInfoPtr user_info = user_view->current_user()->Clone(); |
| user_info->basic_user_info->display_name = display_name; |
| user_view->UpdateForUser(user_info, false /*animate*/); |
| } |
| |
| void LockContentsView::OnPublicSessionLocalesChanged( |
| const AccountId& account_id, |
| const std::vector<mojom::LocaleItemPtr>& locales, |
| const std::string& default_locale, |
| bool show_advanced_view) { |
| LoginUserView* user_view = TryToFindUserView(account_id); |
| if (!user_view || !IsPublicAccountUser(user_view->current_user())) |
| return; |
| |
| mojom::LoginUserInfoPtr user_info = user_view->current_user()->Clone(); |
| user_info->public_account_info->available_locales = mojo::Clone(locales); |
| user_info->public_account_info->default_locale = default_locale; |
| user_info->public_account_info->show_advanced_view = show_advanced_view; |
| user_view->UpdateForUser(user_info, false /*animate*/); |
| } |
| |
| void LockContentsView::OnPublicSessionKeyboardLayoutsChanged( |
| const AccountId& account_id, |
| const std::string& locale, |
| const std::vector<mojom::InputMethodItemPtr>& keyboard_layouts) { |
| // Update expanded view because keyboard layouts is user interactive content. |
| // I.e. user selects a language locale and the corresponding keyboard layouts |
| // will be changed. |
| if (expanded_view_->visible() && |
| expanded_view_->current_user()->basic_user_info->account_id == |
| account_id) { |
| mojom::LoginUserInfoPtr user_info = expanded_view_->current_user()->Clone(); |
| user_info->public_account_info->default_locale = locale; |
| user_info->public_account_info->keyboard_layouts = |
| mojo::Clone(keyboard_layouts); |
| expanded_view_->UpdateForUser(user_info); |
| } |
| |
| LoginUserView* user_view = TryToFindUserView(account_id); |
| if (!user_view || !IsPublicAccountUser(user_view->current_user())) { |
| LOG(ERROR) << "Unable to find public account user."; |
| return; |
| } |
| |
| mojom::LoginUserInfoPtr user_info = user_view->current_user()->Clone(); |
| // Skip updating keyboard layouts if |locale| is not the default locale |
| // of the user. I.e. user changed the default locale in the expanded view, |
| // and it should be handled by expanded view. |
| if (user_info->public_account_info->default_locale != locale) |
| return; |
| |
| user_info->public_account_info->keyboard_layouts = |
| mojo::Clone(keyboard_layouts); |
| user_view->UpdateForUser(user_info, false /*animate*/); |
| } |
| |
| void LockContentsView::OnDetachableBasePairingStatusChanged( |
| DetachableBasePairingStatus pairing_status) { |
| // If the current big user is public account user, or the base is not paired, |
| // or the paired base matches the last used by the current user, the |
| // detachable base error bubble should be hidden. Otherwise, the bubble should |
| // be shown. |
| if (!CurrentBigUserView() || !CurrentBigUserView()->auth_user() || |
| pairing_status == DetachableBasePairingStatus::kNone || |
| (pairing_status == DetachableBasePairingStatus::kAuthenticated && |
| detachable_base_model_->PairedBaseMatchesLastUsedByUser( |
| *CurrentBigUserView()->GetCurrentUser()->basic_user_info))) { |
| detachable_base_error_bubble_->Close(); |
| return; |
| } |
| |
| auth_error_bubble_->Close(); |
| |
| base::string16 error_text = |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_ERROR_DETACHABLE_BASE_CHANGED); |
| |
| views::Label* label = |
| new views::Label(error_text, views::style::CONTEXT_MESSAGE_BOX_BODY_TEXT, |
| views::style::STYLE_PRIMARY); |
| label->SetMultiLine(true); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label->SetEnabledColor(SK_ColorWHITE); |
| |
| detachable_base_error_bubble_->ShowErrorBubble( |
| label, CurrentBigUserView()->auth_user()->password_view() /*anchor_view*/, |
| LoginBubble::kFlagPersistent); |
| |
| // Remove the focus from the password field, to make user less likely to enter |
| // the password without seeing the warning about detachable base change. |
| if (GetWidget()->IsActive()) |
| GetWidget()->GetFocusManager()->ClearFocus(); |
| } |
| |
| void LockContentsView::OnFingerprintUnlockStateChanged( |
| const AccountId& account_id, |
| mojom::FingerprintUnlockState state) { |
| UserState* user_state = FindStateForUser(account_id); |
| if (!user_state) |
| return; |
| |
| user_state->fingerprint_state = state; |
| LoginBigUserView* big_view = |
| TryToFindBigUser(account_id, true /*require_auth_active*/); |
| if (!big_view || !big_view->auth_user()) |
| return; |
| |
| big_view->auth_user()->SetFingerprintState(user_state->fingerprint_state); |
| LayoutAuth(big_view, nullptr /*opt_to_hide*/, true /*animate*/); |
| } |
| |
| void LockContentsView::SetAvatarForUser(const AccountId& account_id, |
| const mojom::UserAvatarPtr& avatar) { |
| auto replace = [&](const mojom::LoginUserInfoPtr& user) { |
| auto changed = user->Clone(); |
| changed->basic_user_info->avatar = avatar->Clone(); |
| return changed; |
| }; |
| |
| LoginBigUserView* big = |
| TryToFindBigUser(account_id, false /*require_auth_active*/); |
| if (big) { |
| big->UpdateForUser(replace(big->GetCurrentUser())); |
| return; |
| } |
| |
| LoginUserView* user = |
| users_list_ ? users_list_->GetUserView(account_id) : nullptr; |
| if (user) { |
| user->UpdateForUser(replace(user->current_user()), false /*animate*/); |
| return; |
| } |
| } |
| |
| void LockContentsView::OnFocusLeavingLockScreenApps(bool reverse) { |
| if (!reverse || lock_screen_apps_active_) |
| FocusNextWidget(reverse); |
| else |
| FindFirstOrLastFocusableChild(this, reverse)->RequestFocus(); |
| } |
| |
| void LockContentsView::OnFocusLeavingSystemTray(bool reverse) { |
| // This function is called when the system tray is losing focus. We want to |
| // focus the first or last child in this view, or a lock screen app window if |
| // one is active (in which case lock contents should not have focus). In the |
| // later case, still focus lock screen first, to synchronously take focus away |
| // from the system shelf (or tray) - lock shelf view expect the focus to be |
| // taken when it passes it to lock screen view, and can misbehave in case the |
| // focus is kept in it. |
| FindFirstOrLastFocusableChild(this, reverse)->RequestFocus(); |
| |
| if (lock_screen_apps_active_) { |
| Shell::Get()->login_screen_controller()->FocusLockScreenApps(reverse); |
| return; |
| } |
| } |
| |
| void LockContentsView::OnDisplayMetricsChanged(const display::Display& display, |
| uint32_t changed_metrics) { |
| // Ignore all metric changes except rotation. |
| if ((changed_metrics & DISPLAY_METRIC_ROTATION) == 0) |
| return; |
| |
| DoLayout(); |
| } |
| |
| void LockContentsView::OnLockStateChanged(bool locked) { |
| if (!locked) { |
| // Successfully unlock the screen. |
| Shell::Get()->metrics()->login_metrics_recorder()->RecordNumLoginAttempts( |
| unlock_attempt_, true /*success*/); |
| unlock_attempt_ = 0; |
| } |
| } |
| |
| void LockContentsView::OnStateChanged( |
| const keyboard::KeyboardControllerState state) { |
| if (!primary_big_view_) |
| return; |
| |
| if (state == keyboard::KeyboardControllerState::SHOWN || |
| state == keyboard::KeyboardControllerState::HIDDEN) { |
| bool keyboard_will_be_shown = |
| state == keyboard::KeyboardControllerState::SHOWN; |
| // Keyboard state can go from SHOWN -> SomeStateOtherThanShownOrHidden -> |
| // SHOWN when we click on the inactive BigUser while the virtual keyboard is |
| // active. In this case, we should do nothing, since |
| // SwapActiveAuthBetweenPrimaryAndSecondary handles the re-layout. |
| if (keyboard_shown_ == keyboard_will_be_shown) |
| return; |
| keyboard_shown_ = keyboard_will_be_shown; |
| LayoutAuth(CurrentBigUserView(), nullptr /*opt_to_hide*/, |
| false /*animate*/); |
| } |
| } |
| |
| void LockContentsView::SuspendImminent( |
| power_manager::SuspendImminent::Reason reason) { |
| LoginAuthUserView* auth_user = CurrentBigUserView()->auth_user(); |
| if (auth_user) |
| auth_user->password_view()->Clear(); |
| } |
| |
| void LockContentsView::ShowAuthErrorMessageForDebug(int unlock_attempt) { |
| unlock_attempt_ = unlock_attempt; |
| ShowAuthErrorMessage(); |
| } |
| |
| void LockContentsView::FocusNextWidget(bool reverse) { |
| Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow()); |
| // Tell the focus direction to the status area or the shelf so they can focus |
| // the correct child view. |
| if (reverse) { |
| shelf->GetStatusAreaWidget() |
| ->status_area_widget_delegate() |
| ->set_default_last_focusable_child(reverse); |
| Shell::Get()->focus_cycler()->FocusWidget(shelf->GetStatusAreaWidget()); |
| } else { |
| shelf->shelf_widget()->set_default_last_focusable_child(reverse); |
| Shell::Get()->focus_cycler()->FocusWidget(shelf->shelf_widget()); |
| } |
| } |
| |
| void LockContentsView::CreateLowDensityLayout( |
| const std::vector<mojom::LoginUserInfoPtr>& users) { |
| DCHECK_EQ(users.size(), 2u); |
| |
| // Space between auth user and alternative user. |
| main_view_->AddChildView(MakeOrientationViewWithWidths( |
| kLowDensityDistanceBetweenUsersInLandscapeDp, |
| kLowDensityDistanceBetweenUsersInPortraitDp)); |
| |
| // Build auth user. |
| opt_secondary_big_view_ = |
| AllocateLoginBigUserView(users[1], false /*is_primary*/); |
| main_view_->AddChildView(opt_secondary_big_view_); |
| } |
| |
| void LockContentsView::CreateMediumDensityLayout( |
| const std::vector<mojom::LoginUserInfoPtr>& users) { |
| // Insert spacing before (left of) auth. |
| main_view_->AddChildViewAt(MakeOrientationViewWithWidths( |
| kMediumDensityMarginLeftOfAuthUserLandscapeDp, |
| kMediumDensityMarginLeftOfAuthUserPortraitDp), |
| 0); |
| // Insert spacing between auth and user list. |
| main_view_->AddChildView(MakeOrientationViewWithWidths( |
| kMediumDensityDistanceBetweenAuthUserAndUsersLandscapeDp, |
| kMediumDensityDistanceBetweenAuthUserAndUsersPortraitDp)); |
| |
| users_list_ = BuildScrollableUsersListView(users, LoginDisplayStyle::kSmall); |
| main_view_->AddChildView(users_list_); |
| |
| // Insert dynamic spacing on left/right of the content which changes based on |
| // screen rotation and display size. |
| auto* left = new NonAccessibleView(); |
| main_view_->AddChildViewAt(left, 0); |
| auto* right = new NonAccessibleView(); |
| main_view_->AddChildView(right); |
| AddRotationAction(base::BindRepeating( |
| [](views::BoxLayout* layout, views::View* left, views::View* right, |
| bool landscape) { |
| if (landscape) { |
| layout->SetFlexForView(left, 1); |
| layout->SetFlexForView(right, 1); |
| } else { |
| layout->SetFlexForView(left, 2); |
| layout->SetFlexForView(right, 1); |
| } |
| }, |
| main_layout_, left, right)); |
| } |
| |
| void LockContentsView::CreateHighDensityLayout( |
| const std::vector<mojom::LoginUserInfoPtr>& users) { |
| // Insert spacing before and after the auth view. |
| auto* fill = new NonAccessibleView(); |
| main_view_->AddChildViewAt(fill, 0); |
| main_layout_->SetFlexForView(fill, 1); |
| |
| fill = new NonAccessibleView(); |
| main_view_->AddChildView(fill); |
| main_layout_->SetFlexForView(fill, 1); |
| |
| users_list_ = |
| BuildScrollableUsersListView(users, LoginDisplayStyle::kExtraSmall); |
| main_view_->AddChildView(users_list_); |
| } |
| |
| void LockContentsView::DoLayout() { |
| bool landscape = login_views_utils::ShouldShowLandscape(GetWidget()); |
| for (auto& action : rotation_actions_) |
| action.Run(landscape); |
| |
| const display::Display& display = |
| display::Screen::GetScreen()->GetDisplayNearestWindow( |
| GetWidget()->GetNativeWindow()); |
| SetPreferredSize(display.size()); |
| SizeToPreferredSize(); |
| Layout(); |
| } |
| |
| void LockContentsView::LayoutTopHeader() { |
| int preferred_width = dev_channel_info_->GetPreferredSize().width() + |
| note_action_->GetPreferredSize().width(); |
| int preferred_height = |
| std::max(dev_channel_info_->GetPreferredSize().height(), |
| note_action_->GetPreferredSize().height()); |
| top_header_->SetPreferredSize(gfx::Size(preferred_width, preferred_height)); |
| top_header_->SizeToPreferredSize(); |
| top_header_->Layout(); |
| // Position the top header - the origin is offset to the left from the top |
| // right corner of the entire view by the width of this top header view. |
| top_header_->SetPosition(GetLocalBounds().top_right() - |
| gfx::Vector2d(preferred_width, 0)); |
| } |
| |
| void LockContentsView::LayoutPublicSessionView() { |
| gfx::Rect bounds = GetContentsBounds(); |
| bounds.ClampToCenteredSize(expanded_view_->GetPreferredSize()); |
| expanded_view_->SetBoundsRect(bounds); |
| } |
| |
| views::View* LockContentsView::MakeOrientationViewWithWidths(int landscape, |
| int portrait) { |
| auto* view = new MultiSizedView(gfx::Size(landscape, kNonEmptyHeightDp), |
| gfx::Size(portrait, kNonEmptyHeightDp)); |
| AddRotationAction(base::BindRepeating(&MultiSizedView::SwapPreferredSizeTo, |
| base::Unretained(view))); |
| return view; |
| } |
| |
| void LockContentsView::AddRotationAction(const OnRotate& on_rotate) { |
| on_rotate.Run(login_views_utils::ShouldShowLandscape(GetWidget())); |
| rotation_actions_.push_back(on_rotate); |
| } |
| |
| void LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary( |
| bool is_primary) { |
| // Do not allow user-swap during authentication. |
| if (Shell::Get()->login_screen_controller()->IsAuthenticating()) |
| return; |
| |
| if (is_primary) { |
| if (!primary_big_view_->IsAuthEnabled()) { |
| LayoutAuth(primary_big_view_, opt_secondary_big_view_, true /*animate*/); |
| OnBigUserChanged(); |
| } else { |
| primary_big_view_->RequestFocus(); |
| } |
| } else if (!is_primary && opt_secondary_big_view_) { |
| if (!opt_secondary_big_view_->IsAuthEnabled()) { |
| LayoutAuth(opt_secondary_big_view_, primary_big_view_, true /*animate*/); |
| OnBigUserChanged(); |
| } else { |
| opt_secondary_big_view_->RequestFocus(); |
| } |
| } |
| } |
| |
| void LockContentsView::OnAuthenticate(bool auth_success) { |
| if (auth_success) { |
| auth_error_bubble_->Close(); |
| detachable_base_error_bubble_->Close(); |
| |
| // Now that the user has been authenticated, update the user's last used |
| // detachable base (if one is attached). This will prevent further |
| // detachable base change notifications from appearing for this base (until |
| // the user uses another detachable base). |
| if (CurrentBigUserView()->auth_user() && |
| detachable_base_model_->GetPairingStatus() == |
| DetachableBasePairingStatus::kAuthenticated) { |
| detachable_base_model_->SetPairedBaseAsLastUsedByUser( |
| *CurrentBigUserView()->GetCurrentUser()->basic_user_info); |
| } |
| } else { |
| ++unlock_attempt_; |
| ShowAuthErrorMessage(); |
| } |
| } |
| |
| LockContentsView::UserState* LockContentsView::FindStateForUser( |
| const AccountId& user) { |
| for (UserState& state : users_) { |
| if (state.account_id == user) |
| return &state; |
| } |
| |
| return nullptr; |
| } |
| |
| void LockContentsView::LayoutAuth(LoginBigUserView* to_update, |
| LoginBigUserView* opt_to_hide, |
| bool animate) { |
| DCHECK(to_update); |
| UpdateAuthForAuthUser(to_update->auth_user(), |
| opt_to_hide ? opt_to_hide->auth_user() : nullptr, |
| animate); |
| UpdateAuthForPublicAccount( |
| to_update->public_account(), |
| opt_to_hide ? opt_to_hide->public_account() : nullptr, animate); |
| } |
| |
| void LockContentsView::SwapToBigUser(int user_index) { |
| // Do not allow user-swap during authentication. |
| if (Shell::Get()->login_screen_controller()->IsAuthenticating()) |
| return; |
| |
| DCHECK(users_list_); |
| LoginUserView* view = users_list_->user_view_at(user_index); |
| DCHECK(view); |
| mojom::LoginUserInfoPtr previous_big_user = |
| primary_big_view_->GetCurrentUser()->Clone(); |
| mojom::LoginUserInfoPtr new_big_user = view->current_user()->Clone(); |
| |
| view->UpdateForUser(previous_big_user, true /*animate*/); |
| primary_big_view_->UpdateForUser(new_big_user); |
| LayoutAuth(primary_big_view_, nullptr, true /*animate*/); |
| OnBigUserChanged(); |
| } |
| |
| void LockContentsView::OnRemoveUserWarningShown(bool is_primary) { |
| Shell::Get()->login_screen_controller()->OnRemoveUserWarningShown(); |
| } |
| |
| void LockContentsView::RemoveUser(bool is_primary) { |
| // Do not allow removing a user during authentication, such as if the user |
| // tried to remove the currently authenticating user. |
| if (Shell::Get()->login_screen_controller()->IsAuthenticating()) |
| return; |
| |
| LoginBigUserView* to_remove = |
| is_primary ? primary_big_view_ : opt_secondary_big_view_; |
| DCHECK(to_remove->GetCurrentUser()->can_remove); |
| AccountId user = to_remove->GetCurrentUser()->basic_user_info->account_id; |
| |
| // Ask chrome to remove the user. |
| Shell::Get()->login_screen_controller()->RemoveUser(user); |
| |
| // Display the new user list less |user|. |
| std::vector<mojom::LoginUserInfoPtr> new_users; |
| if (!is_primary) |
| new_users.push_back(primary_big_view_->GetCurrentUser()->Clone()); |
| if (is_primary && opt_secondary_big_view_) |
| new_users.push_back(opt_secondary_big_view_->GetCurrentUser()->Clone()); |
| if (users_list_) { |
| for (int i = 0; i < users_list_->user_count(); ++i) { |
| new_users.push_back( |
| users_list_->user_view_at(i)->current_user()->Clone()); |
| } |
| } |
| data_dispatcher_->NotifyUsers(new_users); |
| } |
| |
| void LockContentsView::OnBigUserChanged() { |
| const AccountId new_big_user = |
| CurrentBigUserView()->GetCurrentUser()->basic_user_info->account_id; |
| |
| CurrentBigUserView()->RequestFocus(); |
| |
| Shell::Get()->login_screen_controller()->OnFocusPod(new_big_user); |
| UpdateEasyUnlockIconForUser(new_big_user); |
| |
| if (unlock_attempt_ > 0) { |
| // Times a password was incorrectly entered until user gives up (change |
| // user pod). |
| Shell::Get()->metrics()->login_metrics_recorder()->RecordNumLoginAttempts( |
| unlock_attempt_, false /*success*/); |
| |
| // Reset unlock attempt when the auth user changes. |
| unlock_attempt_ = 0; |
| } |
| |
| // The new auth user might have different last used detachable base - make |
| // sure the detachable base pairing error is updated if needed. |
| OnDetachableBasePairingStatusChanged( |
| detachable_base_model_->GetPairingStatus()); |
| |
| if (!detachable_base_error_bubble_->IsVisible()) |
| CurrentBigUserView()->RequestFocus(); |
| } |
| |
| void LockContentsView::UpdateEasyUnlockIconForUser(const AccountId& user) { |
| // Try to find an big view for |user|. If there is none, there is no state to |
| // update. |
| LoginBigUserView* big_view = |
| TryToFindBigUser(user, false /*require_auth_active*/); |
| if (!big_view || !big_view->auth_user()) |
| return; |
| |
| UserState* state = FindStateForUser(user); |
| DCHECK(state); |
| |
| // Hide easy unlock icon if there is no data is available. |
| if (!state->easy_unlock_state) { |
| big_view->auth_user()->SetEasyUnlockIcon(mojom::EasyUnlockIconId::NONE, |
| base::string16()); |
| return; |
| } |
| |
| // TODO(jdufault): Make easy unlock backend always send aria_label, right now |
| // it is only sent if there is no tooltip. |
| base::string16 accessibility_label = state->easy_unlock_state->aria_label; |
| if (accessibility_label.empty()) |
| accessibility_label = state->easy_unlock_state->tooltip; |
| |
| big_view->auth_user()->SetEasyUnlockIcon(state->easy_unlock_state->icon, |
| accessibility_label); |
| } |
| |
| LoginBigUserView* LockContentsView::CurrentBigUserView() { |
| if (opt_secondary_big_view_ && opt_secondary_big_view_->IsAuthEnabled()) { |
| DCHECK(!primary_big_view_->IsAuthEnabled()); |
| return opt_secondary_big_view_; |
| } |
| |
| return primary_big_view_; |
| } |
| |
| void LockContentsView::ShowAuthErrorMessage() { |
| LoginBigUserView* big_view = CurrentBigUserView(); |
| if (!big_view->auth_user()) |
| return; |
| |
| // Show gaia signin if this is login and the user has failed too many times. |
| if (screen_type_ == LockScreen::ScreenType::kLogin && |
| unlock_attempt_ >= kLoginAttemptsBeforeGaiaDialog) { |
| Shell::Get()->login_screen_controller()->ShowGaiaSignin( |
| true /*can_close*/, |
| big_view->auth_user()->current_user()->basic_user_info->account_id); |
| return; |
| } |
| |
| base::string16 error_text = l10n_util::GetStringUTF16( |
| unlock_attempt_ > 1 ? IDS_ASH_LOGIN_ERROR_AUTHENTICATING_2ND_TIME |
| : IDS_ASH_LOGIN_ERROR_AUTHENTICATING); |
| ImeController* ime_controller = Shell::Get()->ime_controller(); |
| if (ime_controller->IsCapsLockEnabled()) { |
| error_text += base::ASCIIToUTF16(" ") + |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_ERROR_CAPS_LOCK_HINT); |
| } |
| |
| base::Optional<int> bold_start; |
| int bold_length = 0; |
| // Display a hint to switch keyboards if there are other active input |
| // methods. |
| if (ime_controller->available_imes().size() > 1) { |
| error_text += base::ASCIIToUTF16(" "); |
| bold_start = error_text.length(); |
| base::string16 shortcut = |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_KEYBOARD_SWITCH_SHORTCUT); |
| bold_length = shortcut.length(); |
| |
| size_t shortcut_offset_in_string; |
| error_text += |
| l10n_util::GetStringFUTF16(IDS_ASH_LOGIN_ERROR_KEYBOARD_SWITCH_HINT, |
| shortcut, &shortcut_offset_in_string); |
| *bold_start += shortcut_offset_in_string; |
| } |
| |
| views::StyledLabel* label = new views::StyledLabel(error_text, this); |
| MakeSectionBold(label, error_text, bold_start, bold_length); |
| label->set_auto_color_readability_enabled(false); |
| |
| auth_error_bubble_->ShowErrorBubble( |
| label, big_view->auth_user()->password_view() /*anchor_view*/, |
| LoginBubble::kFlagsNone); |
| } |
| |
| void LockContentsView::OnEasyUnlockIconHovered() { |
| LoginBigUserView* big_view = CurrentBigUserView(); |
| if (!big_view->auth_user()) |
| return; |
| |
| UserState* state = |
| FindStateForUser(big_view->GetCurrentUser()->basic_user_info->account_id); |
| DCHECK(state); |
| mojom::EasyUnlockIconOptionsPtr& easy_unlock_state = state->easy_unlock_state; |
| DCHECK(easy_unlock_state); |
| |
| if (!easy_unlock_state->tooltip.empty()) { |
| tooltip_bubble_->ShowTooltip( |
| easy_unlock_state->tooltip, |
| big_view->auth_user()->password_view() /*anchor_view*/); |
| } |
| } |
| |
| void LockContentsView::OnEasyUnlockIconTapped() { |
| UserState* state = FindStateForUser( |
| CurrentBigUserView()->GetCurrentUser()->basic_user_info->account_id); |
| DCHECK(state); |
| mojom::EasyUnlockIconOptionsPtr& easy_unlock_state = state->easy_unlock_state; |
| DCHECK(easy_unlock_state); |
| |
| if (easy_unlock_state->hardlock_on_click) { |
| AccountId user = |
| CurrentBigUserView()->GetCurrentUser()->basic_user_info->account_id; |
| Shell::Get()->login_screen_controller()->HardlockPod(user); |
| // TODO(jdufault): This should get called as a result of HardlockPod. |
| OnClickToUnlockEnabledForUserChanged(user, false /*enabled*/); |
| } |
| } |
| |
| keyboard::KeyboardController* LockContentsView::GetKeyboardController() const { |
| return GetWidget() ? GetKeyboardControllerForWidget(GetWidget()) : nullptr; |
| } |
| |
| void LockContentsView::OnPublicAccountTapped(bool is_primary) { |
| // Set the public account user to be the active user. |
| SwapActiveAuthBetweenPrimaryAndSecondary(is_primary); |
| |
| // Update expanded_view_ in case CurrentBigUserView has changed. |
| // 1. It happens when the active big user is changed. For example both |
| // primary and secondary big user are public account and user switches from |
| // primary to secondary. |
| // 2. LoginUserInfo in the big user could be changed if we get updates from |
| // OnPublicSessionDisplayNameChanged and OnPublicSessionLocalesChanged. |
| expanded_view_->UpdateForUser(CurrentBigUserView()->GetCurrentUser()); |
| SetDisplayStyle(DisplayStyle::kExclusivePublicAccountExpandedView); |
| } |
| |
| LoginBigUserView* LockContentsView::AllocateLoginBigUserView( |
| const mojom::LoginUserInfoPtr& user, |
| bool is_primary) { |
| LoginAuthUserView::Callbacks auth_user_callbacks; |
| auth_user_callbacks.on_auth = base::BindRepeating( |
| &LockContentsView::OnAuthenticate, base::Unretained(this)), |
| auth_user_callbacks.on_tap = base::BindRepeating( |
| &LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary, |
| base::Unretained(this), is_primary), |
| auth_user_callbacks.on_remove_warning_shown = |
| base::BindRepeating(&LockContentsView::OnRemoveUserWarningShown, |
| base::Unretained(this), is_primary); |
| auth_user_callbacks.on_remove = base::BindRepeating( |
| &LockContentsView::RemoveUser, base::Unretained(this), is_primary); |
| auth_user_callbacks.on_easy_unlock_icon_hovered = base::BindRepeating( |
| &LockContentsView::OnEasyUnlockIconHovered, base::Unretained(this)); |
| auth_user_callbacks.on_easy_unlock_icon_tapped = base::BindRepeating( |
| &LockContentsView::OnEasyUnlockIconTapped, base::Unretained(this)); |
| |
| LoginPublicAccountUserView::Callbacks public_account_callbacks; |
| public_account_callbacks.on_tap = auth_user_callbacks.on_tap; |
| public_account_callbacks.on_public_account_tapped = |
| base::BindRepeating(&LockContentsView::OnPublicAccountTapped, |
| base::Unretained(this), is_primary); |
| return new LoginBigUserView(user, auth_user_callbacks, |
| public_account_callbacks); |
| } |
| |
| LoginBigUserView* LockContentsView::TryToFindBigUser(const AccountId& user, |
| bool require_auth_active) { |
| LoginBigUserView* view = nullptr; |
| |
| // Find auth instance. |
| if (primary_big_view_ && |
| primary_big_view_->GetCurrentUser()->basic_user_info->account_id == |
| user) { |
| view = primary_big_view_; |
| } else if (opt_secondary_big_view_ && |
| opt_secondary_big_view_->GetCurrentUser() |
| ->basic_user_info->account_id == user) { |
| view = opt_secondary_big_view_; |
| } |
| |
| // Make sure auth instance is active if required. |
| if (require_auth_active && view && !view->IsAuthEnabled()) |
| view = nullptr; |
| |
| return view; |
| } |
| |
| LoginUserView* LockContentsView::TryToFindUserView(const AccountId& user) { |
| // Try to find |user| in big user view first. |
| LoginBigUserView* big_view = |
| TryToFindBigUser(user, false /*require_auth_active*/); |
| if (big_view) |
| return big_view->GetUserView(); |
| |
| // Try to find |user| in users_list_. |
| return users_list_->GetUserView(user); |
| } |
| |
| ScrollableUsersListView* LockContentsView::BuildScrollableUsersListView( |
| const std::vector<mojom::LoginUserInfoPtr>& users, |
| LoginDisplayStyle display_style) { |
| auto* view = new ScrollableUsersListView( |
| users, |
| base::BindRepeating(&LockContentsView::SwapToBigUser, |
| base::Unretained(this)), |
| display_style); |
| view->ClipHeightTo(view->contents()->size().height(), size().height()); |
| return view; |
| } |
| |
| void LockContentsView::UpdateAuthForPublicAccount( |
| LoginPublicAccountUserView* opt_to_update, |
| LoginPublicAccountUserView* opt_to_hide, |
| bool animate) { |
| if (opt_to_update) |
| opt_to_update->SetAuthEnabled(true /*enabled*/, animate); |
| if (opt_to_hide) |
| opt_to_hide->SetAuthEnabled(false /*enabled*/, animate); |
| |
| Layout(); |
| } |
| |
| void LockContentsView::UpdateAuthForAuthUser(LoginAuthUserView* opt_to_update, |
| LoginAuthUserView* opt_to_hide, |
| bool animate) { |
| // Capture animation metadata before we changing state. |
| if (animate) { |
| if (opt_to_update) |
| opt_to_update->CaptureStateForAnimationPreLayout(); |
| if (opt_to_hide) |
| opt_to_hide->CaptureStateForAnimationPreLayout(); |
| } |
| |
| // Update auth methods for |opt_to_update|. Disable auth on |opt_to_hide|. |
| if (opt_to_update) { |
| UserState* state = FindStateForUser( |
| opt_to_update->current_user()->basic_user_info->account_id); |
| uint32_t to_update_auth; |
| if (state->force_online_sign_in) { |
| to_update_auth = LoginAuthUserView::AUTH_ONLINE_SIGN_IN; |
| } else if (state->disable_auth) { |
| to_update_auth = LoginAuthUserView::AUTH_DISABLED; |
| } else { |
| to_update_auth = LoginAuthUserView::AUTH_PASSWORD; |
| keyboard::KeyboardController* keyboard_controller = |
| GetKeyboardController(); |
| const bool is_keyboard_visible = |
| keyboard_controller ? keyboard_controller->IsKeyboardVisible() |
| : false; |
| if (state->show_pin && !is_keyboard_visible && |
| state->fingerprint_state == |
| mojom::FingerprintUnlockState::UNAVAILABLE) { |
| to_update_auth |= LoginAuthUserView::AUTH_PIN; |
| } |
| if (state->enable_tap_auth) |
| to_update_auth |= LoginAuthUserView::AUTH_TAP; |
| if (state->fingerprint_state != |
| mojom::FingerprintUnlockState::UNAVAILABLE) { |
| to_update_auth |= LoginAuthUserView::AUTH_FINGERPRINT; |
| } |
| } |
| opt_to_update->SetAuthMethods(to_update_auth); |
| } |
| if (opt_to_hide) |
| opt_to_hide->SetAuthMethods(LoginAuthUserView::AUTH_NONE); |
| |
| Layout(); |
| |
| // Apply animations. |
| if (animate) { |
| if (opt_to_update) |
| opt_to_update->ApplyAnimationPostLayout(); |
| if (opt_to_hide) |
| opt_to_hide->ApplyAnimationPostLayout(); |
| } |
| } |
| |
| void LockContentsView::SetDisplayStyle(DisplayStyle style) { |
| const bool show_expanded_view = |
| style == DisplayStyle::kExclusivePublicAccountExpandedView; |
| expanded_view_->SetVisible(show_expanded_view); |
| main_view_->SetVisible(!show_expanded_view); |
| top_header_->SetVisible(!show_expanded_view); |
| Layout(); |
| } |
| |
| void LockContentsView::DisableLockScreenNote() { |
| disable_lock_screen_note_ = true; |
| OnLockScreenNoteStateChanged(mojom::TrayActionState::kNotAvailable); |
| } |
| |
| void LockContentsView::RegisterAccelerators() { |
| // TODO: Add more accelerators that are applicable to login screen. |
| accel_map_[ui::Accelerator(ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)] = |
| AcceleratorAction::kShowFeedback; |
| accel_map_[ui::Accelerator(ui::VKEY_RIGHT, 0)] = |
| AcceleratorAction::kFocusNextUser; |
| accel_map_[ui::Accelerator(ui::VKEY_LEFT, 0)] = |
| AcceleratorAction::kFocusPreviousUser; |
| accel_map_[ui::Accelerator( |
| ui::VKEY_R, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)] = |
| AcceleratorAction::kShowResetScreen; |
| |
| AcceleratorController* controller = Shell::Get()->accelerator_controller(); |
| for (const auto& item : accel_map_) |
| controller->Register({item.first}, this); |
| } |
| |
| void LockContentsView::PerformAction(AcceleratorAction action) { |
| switch (action) { |
| case AcceleratorAction::kShowFeedback: |
| Shell::Get()->login_screen_controller()->ShowFeedback(); |
| return; |
| case AcceleratorAction::kFocusNextUser: |
| FocusNextUser(); |
| return; |
| case AcceleratorAction::kFocusPreviousUser: |
| FocusPreviousUser(); |
| return; |
| case AcceleratorAction::kShowResetScreen: |
| Shell::Get()->login_screen_controller()->ShowResetScreen(); |
| return; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| } // namespace ash |