blob: d948b6744f8f4efd0c7da64a15ead9726c8d877e [file] [log] [blame]
// 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_impl.h"
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/focus_cycler.h"
#include "ash/ime/ime_controller_impl.h"
#include "ash/login/login_screen_controller.h"
#include "ash/login/ui/bottom_status_indicator.h"
#include "ash/login/ui/lock_screen.h"
#include "ash/login/ui/lock_screen_media_controls_view.h"
#include "ash/login/ui/login_auth_user_view.h"
#include "ash/login/ui/login_big_user_view.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/system_label_button.h"
#include "ash/login/ui/views_utils.h"
#include "ash/media/media_controller_impl.h"
#include "ash/public/cpp/ash_switches.h"
#include "ash/public/cpp/child_accounts/parent_access_controller.h"
#include "ash/public/cpp/login_accelerators.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.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/style/ash_color_provider.h"
#include "ash/system/model/enterprise_domain_model.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/power/power_button_controller.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 "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/components/proximity_auth/public/mojom/auth_type.mojom.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_type.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/base/user_activity/user_activity_observer.h"
#include "ui/chromeos/devicetype_utils.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/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/button.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/metadata/metadata_impl_macros.h"
#include "ui/views/style/typography.h"
#include "ui/views/vector_icons.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;
constexpr int kMediaControlsSpacingThreshold = 1280;
constexpr int kMediaControlsSmallSpaceFactor = 3;
constexpr int kMediaControlsLargeSpaceFactor = 5;
// 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;
// Horizontal and vertical padding of auth error bubble.
constexpr int kHorizontalPaddingAuthErrorBubbleDp = 8;
constexpr int kVerticalPaddingAuthErrorBubbleDp = 8;
// Spacing between the bottom status indicator and the shelf.
constexpr int kBottomStatusIndicatorBottomMarginDp = 16;
// Spacing between icon and text in the bottom status indicator.
constexpr int kBottomStatusIndicatorChildSpacingDp = 8;
// Spacing between child of LoginBaseBubbleView.
constexpr int kBubbleBetweenChildSpacingDp = 16;
// Border radius of the rounded bubble.
constexpr int kBubbleBorderRadius = 8;
// Width of the management bubble.
constexpr int kManagementBubbleWidth = 400;
// Width of the user adding screen
constexpr int kUserAddingScreenIndicatorWidth = 512;
// Distance from the top of the user view to the user icon.
constexpr int kDistanceFromTopOfBigUserViewToUserIconDp = 24;
// Distance from the bottom of the user adding screen indicator to the user
// icon.
constexpr int kDistanceFromBottomOfIndicatorToUserIconDp =
96 - kDistanceFromTopOfBigUserViewToUserIconDp;
// Min distance from the top of the screen to the top of the user adding screen
// indicator.
constexpr int kMinDistanceFromTopOfScreenToIndicatorDp = 8;
// Padding around the login screen bubble view.
constexpr int kBubblePaddingDp = 16;
// Size of the tooltip view info icon.
constexpr int kInfoIconSizeDp = 20;
// Horizontal and vertical padding of login tooltip view.
constexpr int kHorizontalPaddingLoginTooltipViewDp = 8;
constexpr int kVerticalPaddingLoginTooltipViewDp = 8;
// Maximum width of the management bubble label.
constexpr int kManagementBubbleLabelMaxWidth =
kManagementBubbleWidth - 2 * kBubblePaddingDp - kInfoIconSizeDp -
kBubbleBetweenChildSpacingDp;
constexpr char kAuthErrorContainerName[] = "AuthErrorContainer";
// Sets the preferred width for |view| with an arbitrary height.
void SetPreferredWidthForView(views::View* view, int width) {
view->SetPreferredSize(gfx::Size(width, kNonEmptyHeightDp));
}
// Focuses the first or last focusable child of |root|. If |reverse| is false,
// this focuses the first focusable child. If |reverse| is true, this focuses
// the last focusable child.
void FocusFirstOrLastFocusableChild(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;
views::View* focusable_view = 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);
if (focusable_view) {
focusable_view->AboutToRequestFocusFromTabTraversal(reverse);
focusable_view->RequestFocus();
}
}
// 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->GetFontList().Derive(
0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::BOLD);
}
style.override_color = AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary);
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());
}
keyboard::KeyboardUIController* GetKeyboardControllerForWidget(
const views::Widget* widget) {
auto* keyboard_ui_controller = keyboard::KeyboardUIController::Get();
if (!keyboard_ui_controller->IsEnabled())
return nullptr;
aura::Window* keyboard_window = keyboard_ui_controller->GetRootWindow();
aura::Window* this_window = widget->GetNativeWindow()->GetRootWindow();
return keyboard_window == this_window ? keyboard_ui_controller : nullptr;
}
bool IsPublicAccountUser(const LoginUserInfo& user) {
return user.basic_user_info.type == user_manager::USER_TYPE_PUBLIC_ACCOUNT;
}
bool IsTabletMode() {
return Shell::Get()->tablet_mode_controller()->InTabletMode();
}
//
// Computes a layout described as follows:
//
// l L R r
//
// L R go from [0, L/R_max_fixed_width]
// l and r go from [0, inf]
//
// First, width is distributed to L and R up to their maximum widths. If there
// is not enough width for them, space will be distributed evenly in the same
// ratio as their original sizes.
//
// If L and R are at max width, l and r will receive all remaining space in the
// specified relative weighting.
//
// l -> left_flex_weight
// L -> left_max_fixed_width
// R -> right_max_fixed_width
// r -> right_flex_weight
//
// Output data is in the member variables.
//
struct MediumViewLayout {
MediumViewLayout(int width,
int left_flex_weight,
int left_max_fixed_width,
int right_max_fixed_width,
int right_flex_weight) {
// No space to distribute.
if (width <= 0)
return;
auto set_values_from_weight = [](int width, float weight_a, float weight_b,
int* value_a, int* value_b) {
float total_weight = weight_a + weight_b;
*value_a = width * (weight_a / total_weight);
// Subtract to avoid floating point rounding errors, ie, guarantee that
// that |value_a + value_b = width|.
*value_b = width - *value_a;
};
int flex_width = width - (left_max_fixed_width + right_max_fixed_width);
if (flex_width < 0) {
// No flex available, distribute to fixed width only
set_values_from_weight(width, left_max_fixed_width, right_max_fixed_width,
&left_fixed_width, &right_fixed_width);
DCHECK_EQ(width, left_fixed_width + right_fixed_width);
} else {
// Flex is available; fixed goes to maximum size, extra goes to flex.
left_fixed_width = left_max_fixed_width;
right_fixed_width = right_max_fixed_width;
set_values_from_weight(flex_width, left_flex_weight, right_flex_weight,
&left_flex_width, &right_flex_width);
DCHECK_EQ(flex_width, left_flex_width + right_flex_width);
}
}
int left_fixed_width = 0;
int right_fixed_width = 0;
int left_flex_width = 0;
int right_flex_width = 0;
};
class UserAddingScreenIndicator : public views::View {
public:
UserAddingScreenIndicator() {
views::BoxLayout* layout_manager =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(kBubblePaddingDp), kBubbleBetweenChildSpacingDp));
layout_manager->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
views::ImageView* info_icon = new views::ImageView();
info_icon->SetPreferredSize(gfx::Size(kInfoIconSizeDp, kInfoIconSizeDp));
info_icon->SetImage(gfx::CreateVectorIcon(
views::kInfoIcon,
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary)));
AddChildView(info_icon);
base::string16 message =
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_USER_ADDING_BANNER);
views::Label* label_ = login_views_utils::CreateBubbleLabel(message, this);
label_->SetText(message);
AddChildView(label_);
SetPaintToLayer();
SkColor background_color = AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kTransparent80);
layer()->SetBackgroundBlur(
static_cast<float>(AshColorProvider::LayerBlurSigma::kBlurDefault));
SetBackground(views::CreateRoundedRectBackground(background_color,
kBubbleBorderRadius));
layer()->SetFillsBoundsOpaquely(false);
}
UserAddingScreenIndicator(const UserAddingScreenIndicator&) = delete;
UserAddingScreenIndicator& operator=(const UserAddingScreenIndicator&) =
delete;
~UserAddingScreenIndicator() override = default;
// views::View:
gfx::Size CalculatePreferredSize() const override {
return gfx::Size(kUserAddingScreenIndicatorWidth,
GetHeightForWidth(kUserAddingScreenIndicatorWidth));
}
};
} // namespace
class LockContentsView::AuthErrorBubble : public LoginErrorBubble {
public:
AuthErrorBubble() {
set_positioning_strategy(PositioningStrategy::kTryAfterThenBefore);
SetPadding(kHorizontalPaddingAuthErrorBubbleDp,
kVerticalPaddingAuthErrorBubbleDp);
}
};
class LockContentsView::ManagementBubble : public LoginTooltipView {
public:
ManagementBubble(const base::string16& message, views::View* anchor_view)
: LoginTooltipView(message, anchor_view) {
views::BoxLayout* layout_manager =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(kBubblePaddingDp), kBubbleBetweenChildSpacingDp));
layout_manager->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
label()->SetMaximumWidth(kManagementBubbleLabelMaxWidth);
set_positioning_strategy(PositioningStrategy::kShowAbove);
}
// LoginBaseBubbleView:
gfx::Size CalculatePreferredSize() const override {
return gfx::Size(kManagementBubbleWidth,
GetHeightForWidth(kManagementBubbleWidth));
}
};
class LockContentsView::AutoLoginUserActivityHandler
: public ui::UserActivityObserver {
public:
AutoLoginUserActivityHandler() {
observer_.Add(ui::UserActivityDetector::Get());
}
~AutoLoginUserActivityHandler() override = default;
void OnUserActivity(const ui::Event* event) override {
if (Shell::Get()->login_screen_controller()) {
Shell::Get()->login_screen_controller()->NotifyUserActivity();
}
}
private:
ScopedObserver<ui::UserActivityDetector, ui::UserActivityObserver> observer_{
this};
DISALLOW_COPY_AND_ASSIGN(AutoLoginUserActivityHandler);
};
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_;
}
AccountId LockContentsView::TestApi::focused_user() const {
if (view_->CurrentBigUserView()->public_account()) {
return view_->CurrentBigUserView()
->public_account()
->current_user()
.basic_user_info.account_id;
}
return view_->CurrentBigUserView()
->auth_user()
->current_user()
.basic_user_info.account_id;
}
ScrollableUsersListView* LockContentsView::TestApi::users_list() const {
return view_->users_list_;
}
LockScreenMediaControlsView* LockContentsView::TestApi::media_controls_view()
const {
return view_->media_controls_view_;
}
views::View* LockContentsView::TestApi::note_action() const {
return view_->note_action_;
}
views::View* LockContentsView::TestApi::tooltip_bubble() const {
return view_->tooltip_bubble_;
}
views::View* LockContentsView::TestApi::management_bubble() const {
return view_->management_bubble_;
}
LoginErrorBubble* LockContentsView::TestApi::auth_error_bubble() const {
return view_->auth_error_bubble_;
}
LoginErrorBubble* LockContentsView::TestApi::detachable_base_error_bubble()
const {
return view_->detachable_base_error_bubble_;
}
LoginErrorBubble* LockContentsView::TestApi::warning_banner_bubble() const {
return view_->warning_banner_bubble_;
}
LoginErrorBubble*
LockContentsView::TestApi::supervised_user_deprecation_bubble() const {
return view_->supervised_user_deprecation_bubble_;
}
views::View* LockContentsView::TestApi::user_adding_screen_indicator() const {
return view_->user_adding_screen_indicator_;
}
views::View* LockContentsView::TestApi::system_info() const {
return view_->system_info_;
}
views::View* LockContentsView::TestApi::bottom_status_indicator() const {
return view_->bottom_status_indicator_;
}
LockContentsView::BottomIndicatorState
LockContentsView::TestApi::bottom_status_indicator_status() const {
return view_->bottom_status_indicator_status_;
}
LoginExpandedPublicAccountView* LockContentsView::TestApi::expanded_view()
const {
return view_->expanded_view_;
}
views::View* LockContentsView::TestApi::main_view() const {
return view_->main_view_;
}
const std::vector<LockContentsView::UserState>&
LockContentsView::TestApi::users() const {
return view_->users_;
}
LoginBigUserView* LockContentsView::TestApi::FindBigUser(
const AccountId& account_id) {
LoginBigUserView* big_view =
view_->TryToFindBigUser(account_id, false /*require_auth_active*/);
if (big_view)
return big_view;
LoginUserView* user_view = view_->TryToFindUserView(account_id);
if (!user_view) {
DLOG(ERROR) << "Could not find user: " << account_id.Serialize();
return nullptr;
}
LoginUserView::TestApi user_view_api(user_view);
user_view_api.OnTap();
return view_->TryToFindBigUser(account_id, false /*require_auth_active*/);
}
LoginUserView* LockContentsView::TestApi::FindUserView(
const AccountId& account_id) {
if (view_->expanded_view_ && view_->expanded_view_->GetVisible()) {
LoginExpandedPublicAccountView::TestApi expanded_test(
view_->expanded_view_);
return expanded_test.user_view();
}
return view_->TryToFindUserView(account_id);
}
bool LockContentsView::TestApi::RemoveUser(const AccountId& account_id) {
LoginBigUserView* big_view = FindBigUser(account_id);
if (!big_view)
return false;
if (!big_view->GetCurrentUser().can_remove)
return false;
LoginBigUserView::TestApi user_api(big_view);
user_api.Remove();
return true;
}
bool LockContentsView::TestApi::IsOobeDialogVisible() const {
return view_->oobe_dialog_visible_;
}
LockContentsView::UserState::UserState(const LoginUserInfo& user_info)
: account_id(user_info.basic_user_info.account_id) {
fingerprint_state = user_info.fingerprint_state;
if (user_info.auth_type == proximity_auth::mojom::AuthType::ONLINE_SIGN_IN)
force_online_sign_in = true;
show_pin_pad_for_password = user_info.show_pin_pad_for_password;
disable_auth = !user_info.is_multiprofile_allowed &&
Shell::Get()->session_controller()->GetSessionState() ==
session_manager::SessionState::LOGIN_SECONDARY;
}
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(),
screen_type_(screen_type),
data_dispatcher_(data_dispatcher),
detachable_base_model_(std::move(detachable_base_model)) {
if (screen_type == LockScreen::ScreenType::kLogin)
auto_login_user_activity_handler_ =
std::make_unique<AutoLoginUserActivityHandler>();
data_dispatcher_->AddObserver(this);
display_observer_.Add(display::Screen::GetScreen());
Shell::Get()->system_tray_notifier()->AddSystemTrayFocusObserver(this);
keyboard::KeyboardUIController::Get()->AddObserver(this);
// 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_ = AddChildView(std::make_unique<NonAccessibleView>());
// The top header view.
top_header_ = AddChildView(std::make_unique<views::View>());
auto top_header_layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal);
top_header_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kEnd);
top_header_->SetLayoutManager(std::move(top_header_layout));
system_info_ = top_header_->AddChildView(std::make_unique<views::View>());
auto* system_info_layout =
system_info_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(6, 8)));
system_info_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kEnd);
system_info_->SetVisible(false);
// The bottom status indicator view.
bottom_status_indicator_ =
AddChildView(std::make_unique<BottomStatusIndicator>(
base::BindRepeating(&LockContentsView::OnBottomStatusIndicatorTapped,
weak_ptr_factory_.GetWeakPtr())));
auto bottom_status_indicator_layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
kBottomStatusIndicatorChildSpacingDp);
bottom_status_indicator_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kEnd);
bottom_status_indicator_->SetLayoutManager(
std::move(bottom_status_indicator_layout));
std::string enterprise_domain_manager = Shell::Get()
->system_tray_model()
->enterprise_domain()
->enterprise_domain_manager();
if (!enterprise_domain_manager.empty())
ShowEnterpriseDomainManager(enterprise_domain_manager);
note_action_ = top_header_->AddChildView(
std::make_unique<NoteActionLaunchButton>(initial_note_action_state));
// Public Session expanded view.
expanded_view_ =
AddChildView(std::make_unique<LoginExpandedPublicAccountView>(
base::BindRepeating(&LockContentsView::SetDisplayStyle,
base::Unretained(this), DisplayStyle::kAll)));
expanded_view_->SetVisible(false);
supervised_user_deprecation_bubble_ =
AddChildView(std::make_unique<LoginErrorBubble>());
supervised_user_deprecation_bubble_->set_persistent(true);
detachable_base_error_bubble_ =
AddChildView(std::make_unique<LoginErrorBubble>());
detachable_base_error_bubble_->set_persistent(true);
tooltip_bubble_ = AddChildView(std::make_unique<LoginTooltipView>(
base::UTF8ToUTF16("") /*message*/, nullptr /*anchor_view*/));
tooltip_bubble_->set_positioning_strategy(
LoginBaseBubbleView::PositioningStrategy::kTryBeforeThenAfter);
tooltip_bubble_->SetPadding(kHorizontalPaddingLoginTooltipViewDp,
kVerticalPaddingLoginTooltipViewDp);
management_bubble_ = new ManagementBubble(
l10n_util::GetStringFUTF16(IDS_ASH_LOGIN_ENTERPRISE_MANAGED_POP_UP,
ui::GetChromeOSDeviceName(),
base::UTF8ToUTF16(enterprise_domain_manager)),
bottom_status_indicator_);
AddChildView(management_bubble_);
warning_banner_bubble_ = AddChildView(std::make_unique<LoginErrorBubble>());
warning_banner_bubble_->set_persistent(true);
auth_error_bubble_ = AddChildView(std::make_unique<AuthErrorBubble>());
if (Shell::Get()->session_controller()->GetSessionState() ==
session_manager::SessionState::LOGIN_SECONDARY) {
user_adding_screen_indicator_ =
AddChildView(std::make_unique<UserAddingScreenIndicator>());
}
OnLockScreenNoteStateChanged(initial_note_action_state);
chromeos::PowerManagerClient::Get()->AddObserver(this);
RegisterAccelerators();
}
LockContentsView::~LockContentsView() {
Shell::Get()->accelerator_controller()->UnregisterAll(this);
data_dispatcher_->RemoveObserver(this);
keyboard::KeyboardUIController::Get()->RemoveObserver(this);
Shell::Get()->system_tray_notifier()->RemoveSystemTrayFocusObserver(this);
// Times a password was incorrectly entered until view is destroyed.
Shell::Get()->metrics()->login_metrics_recorder()->RecordNumLoginAttempts(
false /*success*/, &unlock_attempt_);
chromeos::PowerManagerClient::Get()->RemoveObserver(this);
}
void LockContentsView::FocusNextUser() {
if (users_.empty())
return;
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;
}
if (users_list_) {
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 (users_.empty())
return;
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;
}
if (users_list_) {
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::ShowEnterpriseDomainManager(
const std::string& entreprise_domain_manager) {
if (!chromeos::features::IsLoginDeviceManagementDisclosureEnabled())
return;
bottom_status_indicator_->SetIcon(
chromeos::kEnterpriseIcon,
AshColorProvider::ContentLayerType::kButtonIconColorPrimary);
bottom_status_indicator_->SetText(l10n_util::GetStringFUTF16(
IDS_ASH_LOGIN_MANAGED_DEVICE_INDICATOR, ui::GetChromeOSDeviceName(),
base::UTF8ToUTF16(entreprise_domain_manager)));
bottom_status_indicator_->SetEnabledTextColors(
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kButtonLabelColorPrimary));
bottom_status_indicator_->set_role_for_accessibility(
ax::mojom::Role::kButton);
bottom_status_indicator_status_ = BottomIndicatorState::kManagedDevice;
UpdateBottomStatusIndicatorVisibility();
}
void LockContentsView::ShowAdbEnabled() {
bottom_status_indicator_->SetIcon(
kLockScreenAlertIcon,
AshColorProvider::ContentLayerType::kIconColorAlert);
bottom_status_indicator_->SetText(
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_SCREEN_UNVERIFIED_CODE_WARNING));
bottom_status_indicator_->SetEnabledTextColors(
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorAlert));
bottom_status_indicator_->set_role_for_accessibility(
ax::mojom::Role::kStaticText);
bottom_status_indicator_status_ =
BottomIndicatorState::kAdbSideLoadingEnabled;
UpdateBottomStatusIndicatorVisibility();
}
void LockContentsView::ToggleSystemInfo() {
enable_system_info_if_possible_ = !enable_system_info_if_possible_;
// Whether the system information should be displayed or not might be
// enforced according to policy settings.
bool system_info_visibility = GetSystemInfoVisibility();
if (system_info_visibility != system_info_->GetVisible()) {
system_info_->SetVisible(system_info_visibility);
LayoutTopHeader();
LayoutBottomStatusIndicator();
}
}
void LockContentsView::ShowParentAccessDialog() {
// ParentAccessDialog should only be shown on lock screen from here.
DCHECK(primary_big_view_);
const AccountId account_id =
CurrentBigUserView()->GetCurrentUser().basic_user_info.account_id;
Shell::Get()->parent_access_controller()->ShowWidget(
account_id,
base::BindOnce(&LockContentsView::OnParentAccessValidationFinished,
weak_ptr_factory_.GetWeakPtr(), account_id),
SupervisedAction::kUnlockTimeLimits, false, base::Time::Now());
Shell::Get()->login_screen_controller()->ShowParentAccessButton(false);
}
void LockContentsView::Layout() {
View::Layout();
LayoutTopHeader();
LayoutBottomStatusIndicator();
LayoutUserAddingScreenIndicator();
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();
GetViewAccessibility().OverrideNextFocus(shelf_widget);
GetViewAccessibility().OverridePreviousFocus(shelf->GetStatusAreaWidget());
node_data->SetName(
l10n_util::GetStringUTF16(screen_type_ == LockScreen::ScreenType::kLogin
? IDS_ASH_LOGIN_SCREEN_ACCESSIBLE_NAME
: IDS_ASH_LOCK_SCREEN_ACCESSIBLE_NAME));
node_data->role = ax::mojom::Role::kWindow;
}
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<LoginUserInfo>& users) {
// The debug view will potentially call this method many times. Make sure to
// invalidate any child references.
primary_big_view_ = nullptr;
opt_secondary_big_view_ = nullptr;
users_list_ = nullptr;
middle_spacing_view_ = nullptr;
media_controls_view_ = nullptr;
layout_actions_.clear();
// Removing child views can change focus, which may result in LockContentsView
// getting focused. Make sure to clear internal references before that happens
// so there is not stale-pointer usage. See crbug.com/884402.
main_view_->RemoveAllChildViews(true /*delete_children*/);
// Build user state list. Preserve previous state if the user already exists.
std::vector<UserState> new_users;
for (const LoginUserInfo& user : users) {
UserState* old_state = FindStateForUser(user.basic_user_info.account_id);
if (old_state)
new_users.push_back(std::move(*old_state));
else
new_users.push_back(UserState(user));
}
users_ = std::move(new_users);
// If there are no users, show gaia signin if login.
if (users.empty() && screen_type_ == LockScreen::ScreenType::kLogin) {
Shell::Get()->login_screen_controller()->ShowGaiaSignin(
EmptyAccountId() /*prefilled_account*/);
return;
}
// Allocate layout which is common between all densities.
auto* main_layout =
main_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
main_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
main_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
if (!users.empty()) {
auto primary_big_view =
AllocateLoginBigUserView(users[0], true /*is_primary*/);
// Build layout for additional users.
if (users.size() <= 2)
CreateLowDensityLayout(users, std::move(primary_big_view));
else if (users.size() >= 3 && users.size() <= 6)
CreateMediumDensityLayout(users, std::move(primary_big_view));
else if (users.size() >= 7)
CreateHighDensityLayout(users, main_layout, std::move(primary_big_view));
// |primary_big_view_| must have been set by one of the above functions.
DCHECK(primary_big_view_);
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();
// If one of the child views had focus before we deleted them, then this view
// will get focused. Move focus back to the primary big view.
if (primary_big_view_ && HasFocus())
primary_big_view_->RequestFocus();
}
void LockContentsView::OnUserAvatarChanged(const AccountId& account_id,
const UserAvatar& avatar) {
auto replace = [&avatar](const LoginUserInfo& user) {
auto changed = user;
changed.basic_user_info.avatar = avatar;
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::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;
state->autosubmit_pin_length =
user_manager::known_user::GetUserPinLength(user);
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::OnChallengeResponseAuthEnabledForUserChanged(
const AccountId& user,
bool enabled) {
LockContentsView::UserState* state = FindStateForUser(user);
if (!state) {
LOG(ERROR)
<< "Unable to find user when changing challenge-response auth state to "
<< enabled;
return;
}
state->show_challenge_response_auth = enabled;
LoginBigUserView* big_user =
TryToFindBigUser(user, /*require_auth_active=*/true);
if (big_user && big_user->auth_user())
LayoutAuth(big_user, /*opt_to_hide=*/nullptr, /*animate=*/true);
}
void LockContentsView::OnFingerprintStateChanged(const AccountId& account_id,
FingerprintState 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;
// TODO(crbug.com/893298): Re-enable animation once the error bubble supports
// being displayed on the left. This also requires that we dynamically
// track/update the position of the bubble, or alternatively we set the bubble
// location to the target animation position and not the current position.
bool animate = true;
if (user_state->fingerprint_state ==
FingerprintState::DISABLED_FROM_TIMEOUT) {
animate = false;
}
big_view->auth_user()->SetFingerprintState(user_state->fingerprint_state);
LayoutAuth(big_view, nullptr /*opt_to_hide*/, animate);
}
void LockContentsView::OnFingerprintAuthResult(const AccountId& account_id,
bool success) {
// Make sure the display backlight is not forced off if there is a fingerprint
// authentication attempt. If the display backlight is off, then the device
// will authenticate and dismiss the lock screen but it will not be visible to
// the user.
Shell::Get()->power_button_controller()->StopForcingBacklightsOff();
// |account_id| comes from IPC, make sure it refers to a valid user. The
// fingerprint scan could have also happened while switching users, so the
// associated account is no longer a big user.
LoginBigUserView* big_view =
TryToFindBigUser(account_id, true /*require_auth_active*/);
if (!big_view || !big_view->auth_user())
return;
big_view->auth_user()->NotifyFingerprintAuthResult(success);
}
void LockContentsView::OnAuthEnabledForUser(const AccountId& user) {
LockContentsView::UserState* state = FindStateForUser(user);
if (!state) {
LOG(ERROR) << "Unable to find user when enabling auth.";
return;
}
state->disable_auth = false;
disable_lock_screen_note_ = state->disable_auth;
OnLockScreenNoteStateChanged(
Shell::Get()->tray_action()->GetLockScreenNoteState());
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::OnAuthDisabledForUser(
const AccountId& user,
const AuthDisabledData& auth_disabled_data) {
LockContentsView::UserState* state = FindStateForUser(user);
if (!state) {
LOG(ERROR) << "Unable to find user when disabling auth";
return;
}
state->disable_auth = true;
disable_lock_screen_note_ = state->disable_auth;
OnLockScreenNoteStateChanged(mojom::TrayActionState::kNotAvailable);
if (auth_disabled_data.disable_lock_screen_media) {
Shell::Get()->media_controller()->SuspendMediaSessions();
HideMediaControlsLayout();
}
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*/);
big_user->auth_user()->SetAuthDisabledMessage(auth_disabled_data);
}
}
void LockContentsView::OnSetTpmLockedState(const AccountId& user,
bool is_locked,
base::TimeDelta time_left) {
LockContentsView::UserState* state = FindStateForUser(user);
if (!state) {
LOG(ERROR) << "Unable to find user when setting TPM lock state";
return;
}
state->time_until_tpm_unlock =
is_locked ? base::make_optional(time_left) : base::nullopt;
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::OnTapToUnlockEnabledForUserChanged(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 EasyUnlockIconOptions& icon) {
UserState* state = FindStateForUser(user);
if (!state)
return;
state->easy_unlock_state = icon;
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;
if (tooltip_bubble_->GetVisible())
tooltip_bubble_->Hide();
if (icon.autoshow_tooltip) {
tooltip_bubble_->SetAnchorView(big_user->auth_user()->GetActiveInputView());
tooltip_bubble_->SetText(icon.tooltip);
tooltip_bubble_->Show();
tooltip_bubble_->SetVisible(true);
}
}
void LockContentsView::OnWarningMessageUpdated(const base::string16& message) {
if (message.empty()) {
if (warning_banner_bubble_->GetVisible())
warning_banner_bubble_->Hide();
return;
}
if (!CurrentBigUserView() || !CurrentBigUserView()->auth_user()) {
LOG(ERROR) << "Unable to find the current active big user to show a "
"warning banner.";
return;
}
if (warning_banner_bubble_->GetVisible())
warning_banner_bubble_->Hide();
// Shows warning banner as a persistent error bubble.
warning_banner_bubble_->SetAnchorView(
CurrentBigUserView()->auth_user()->GetActiveInputView());
warning_banner_bubble_->SetTextContent(message);
warning_banner_bubble_->Show();
}
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::OnSystemInfoChanged(
bool show,
bool enforced,
const std::string& os_version_label_text,
const std::string& enterprise_info_text,
const std::string& bluetooth_name,
bool adb_sideloading_enabled) {
// Helper function to create a label for the system info view.
auto create_info_label = []() {
auto label = std::make_unique<views::Label>();
label->SetAutoColorReadabilityEnabled(false);
label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
label->SetFontList(views::Label::GetDefaultFontList().Derive(
-1, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
label->SetSubpixelRenderingEnabled(false);
return label;
};
// Initialize the system info view.
if (system_info_->children().empty()) {
for (int i = 0; i < 3; ++i)
system_info_->AddChildView(create_info_label());
}
if (enforced) {
enable_system_info_enforced_ = show;
} else {
enable_system_info_enforced_ = base::nullopt;
enable_system_info_if_possible_ |= show;
}
bool system_info_visible = GetSystemInfoVisibility();
system_info_->SetVisible(system_info_visible);
auto update_label = [&](size_t index, const std::string& text) {
views::Label* label =
static_cast<views::Label*>(system_info_->children()[index]);
label->SetText(base::UTF8ToUTF16(text));
label->SetVisible(!text.empty());
};
update_label(0, os_version_label_text);
update_label(1, enterprise_info_text);
update_label(2, bluetooth_name);
LayoutTopHeader();
// TODO(crbug.com/1141348): Separate ADB sideloading from system info changed.
// Note that if ADB is enabled and the device is enrolled, only the ADB
// warning message will be displayed.
if (adb_sideloading_enabled)
ShowAdbEnabled();
LayoutBottomStatusIndicator();
}
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;
LoginUserInfo user_info = user_view->current_user();
user_info.basic_user_info.display_name = display_name;
user_view->UpdateForUser(user_info, false /*animate*/);
MaybeUpdateExpandedView(account_id, user_info);
}
void LockContentsView::OnPublicSessionLocalesChanged(
const AccountId& account_id,
const std::vector<LocaleItem>& 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;
LoginUserInfo user_info = user_view->current_user();
user_info.public_account_info->available_locales = 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*/);
MaybeUpdateExpandedView(account_id, user_info);
}
void LockContentsView::OnPublicSessionKeyboardLayoutsChanged(
const AccountId& account_id,
const std::string& locale,
const std::vector<InputMethodItem>& keyboard_layouts) {
LoginUserView* user_view = TryToFindUserView(account_id);
if (!user_view || !IsPublicAccountUser(user_view->current_user())) {
LOG(ERROR) << "Unable to find public account user.";
return;
}
LoginUserInfo user_info = user_view->current_user();
user_info.public_account_info->keyboard_layouts = keyboard_layouts;
// 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) {
user_view->UpdateForUser(user_info, false /*animate*/);
}
user_info.public_account_info->default_locale = locale;
MaybeUpdateExpandedView(account_id, user_info);
}
void LockContentsView::OnPublicSessionShowFullManagementDisclosureChanged(
bool show_full_management_disclosure) {
expanded_view_->SetShowFullManagementDisclosure(
show_full_management_disclosure);
}
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))) {
if (detachable_base_error_bubble_->GetVisible())
detachable_base_error_bubble_->Hide();
return;
}
if (auth_error_bubble_->GetVisible())
auth_error_bubble_->Hide();
base::string16 error_text =
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_ERROR_DETACHABLE_BASE_CHANGED);
detachable_base_error_bubble_->SetTextContent(error_text);
detachable_base_error_bubble_->SetAnchorView(
CurrentBigUserView()->auth_user()->GetActiveInputView());
detachable_base_error_bubble_->Show();
// 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::OnFocusLeavingLockScreenApps(bool reverse) {
if (!reverse || lock_screen_apps_active_)
FocusNextWidget(reverse);
else
FocusFirstOrLastFocusableChild(this, reverse);
}
void LockContentsView::OnOobeDialogStateChanged(OobeDialogState state) {
oobe_dialog_visible_ = state != OobeDialogState::HIDDEN;
extension_ui_visible_ = state == OobeDialogState::EXTENSION_LOGIN;
// Show either oobe dialog or user pods.
if (main_view_)
main_view_->SetVisible(!oobe_dialog_visible_);
GetWidget()->widget_delegate()->SetCanActivate(!oobe_dialog_visible_);
UpdateBottomStatusIndicatorVisibility();
if (!oobe_dialog_visible_ && primary_big_view_)
primary_big_view_->RequestFocus();
}
void LockContentsView::MaybeUpdateExpandedView(const AccountId& account_id,
const LoginUserInfo& user_info) {
if (expanded_view_ && expanded_view_->GetVisible() &&
expanded_view_->current_user().basic_user_info.account_id == account_id) {
expanded_view_->UpdateForUser(user_info);
}
}
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, a lock screen app window if
// one is active (in which case lock contents should not have focus), or the
// OOBE dialog modal if it's active. In the later cases, 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.
FocusFirstOrLastFocusableChild(this, reverse);
if (lock_screen_apps_active_) {
Shell::Get()->login_screen_controller()->FocusLockScreenApps(reverse);
return;
}
if (oobe_dialog_visible_)
Shell::Get()->login_screen_controller()->FocusOobeDialog();
}
void LockContentsView::OnDisplayMetricsChanged(const display::Display& display,
uint32_t changed_metrics) {
// Ignore all metrics except for those listed in |filter|.
uint32_t filter = DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_WORK_AREA |
DISPLAY_METRIC_DEVICE_SCALE_FACTOR |
DISPLAY_METRIC_ROTATION | DISPLAY_METRIC_PRIMARY;
if ((filter & changed_metrics) == 0)
return;
DoLayout();
// Set bounds here so that the lock screen widget always shows up on the
// primary display. Sometimes the widget bounds are incorrect in the case
// where multiple external displays are used. See crbug.com/1031571.
GetWidget()->SetBounds(
display::Screen::GetScreen()->GetPrimaryDisplay().bounds());
}
void LockContentsView::OnKeyboardVisibilityChanged(bool is_visible) {
if (!primary_big_view_ || keyboard_shown_ == is_visible)
return;
keyboard_shown_ = is_visible;
LayoutAuth(CurrentBigUserView(), nullptr /*opt_to_hide*/, true /*animate*/);
}
void LockContentsView::SuspendImminent(
power_manager::SuspendImminent::Reason reason) {
LoginBigUserView* big_user = CurrentBigUserView();
if (big_user && big_user->auth_user())
big_user->auth_user()->password_view()->Clear();
}
void LockContentsView::ShowAuthErrorMessageForDebug(int unlock_attempt) {
unlock_attempt_ = unlock_attempt;
ShowAuthErrorMessage();
}
void LockContentsView::ToggleManagementForUserForDebug(const AccountId& user) {
auto replace = [](const LoginUserInfo& user_info) {
auto changed = user_info;
if (user_info.user_account_manager)
changed.user_account_manager.reset();
else
changed.user_account_manager = "example@example.com";
return changed;
};
LoginBigUserView* big = TryToFindBigUser(user, false /*require_auth_active*/);
if (big) {
big->UpdateForUser(replace(big->GetCurrentUser()));
return;
}
LoginUserView* user_view =
users_list_ ? users_list_->GetUserView(user) : nullptr;
if (user_view) {
user_view->UpdateForUser(replace(user_view->current_user()),
false /*animate*/);
return;
}
}
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::SetLowDensitySpacing(views::View* spacing_middle,
views::View* secondary_view,
int landscape_dist,
int portrait_dist,
bool landscape) {
int total_width = GetPreferredSize().width();
int available_width =
total_width - (primary_big_view_->GetPreferredSize().width() +
secondary_view->GetPreferredSize().width());
if (available_width <= 0) {
SetPreferredWidthForView(spacing_middle, 0);
return;
}
int desired_width = landscape ? landscape_dist : portrait_dist;
SetPreferredWidthForView(spacing_middle,
std::min(available_width, desired_width));
}
void LockContentsView::SetMediaControlsSpacing(bool landscape) {
int total_width = GetPreferredSize().width();
int available_width =
total_width - (primary_big_view_->GetPreferredSize().width() +
media_controls_view_->GetPreferredSize().width());
if (available_width <= 0) {
SetPreferredWidthForView(middle_spacing_view_, 0);
return;
}
int desired_width;
if (!landscape || total_width <= kMediaControlsSpacingThreshold)
desired_width = available_width / kMediaControlsSmallSpaceFactor;
else
desired_width = available_width / kMediaControlsLargeSpaceFactor;
SetPreferredWidthForView(middle_spacing_view_, desired_width);
}
bool LockContentsView::AreMediaControlsEnabled() const {
return screen_type_ == LockScreen::ScreenType::kLock &&
!expanded_view_->GetVisible() &&
Shell::Get()->media_controller()->AreLockScreenMediaKeysEnabled();
}
void LockContentsView::HideMediaControlsLayout() {
DCHECK(middle_spacing_view_);
DCHECK(media_controls_view_);
middle_spacing_view_->SetVisible(false);
media_controls_view_->SetVisible(false);
// Don't allow media keys to be used on lock screen since controls are hidden.
Shell::Get()->media_controller()->SetMediaControlsDismissed(true);
Layout();
}
void LockContentsView::CreateMediaControlsLayout() {
DCHECK(middle_spacing_view_);
DCHECK(media_controls_view_);
media_controls_view_->SetVisible(true);
middle_spacing_view_->SetVisible(true);
// Set |spacing_middle|.
AddDisplayLayoutAction(base::BindRepeating(
&LockContentsView::SetMediaControlsSpacing, base::Unretained(this)));
Layout();
}
void LockContentsView::CreateLowDensityLayout(
const std::vector<LoginUserInfo>& users,
std::unique_ptr<LoginBigUserView> primary_big_view) {
DCHECK_LE(users.size(), 2u);
primary_big_view_ = main_view_->AddChildView(std::move(primary_big_view));
// Build media controls view. Using base::Unretained(this) is safe here
// because these callbacks are used by |media_controls_view_|, which is
// owned by |this|.
LockScreenMediaControlsView::Callbacks media_controls_callbacks;
media_controls_callbacks.media_controls_enabled = base::BindRepeating(
&LockContentsView::AreMediaControlsEnabled, base::Unretained(this));
media_controls_callbacks.hide_media_controls = base::BindRepeating(
&LockContentsView::HideMediaControlsLayout, base::Unretained(this));
media_controls_callbacks.show_media_controls = base::BindRepeating(
&LockContentsView::CreateMediaControlsLayout, base::Unretained(this));
auto media_controls_view =
std::make_unique<LockScreenMediaControlsView>(media_controls_callbacks);
// Space between primary user and media controls.
middle_spacing_view_ =
main_view_->AddChildView(std::make_unique<NonAccessibleView>());
// Media controls view.
media_controls_view_ =
main_view_->AddChildView(std::move(media_controls_view));
media_controls_view_->SetVisible(false);
middle_spacing_view_->SetVisible(false);
if (users.size() > 1) {
// Space between primary user and secondary user.
auto* spacing_middle =
main_view_->AddChildView(std::make_unique<NonAccessibleView>());
// Build secondary auth user.
opt_secondary_big_view_ = main_view_->AddChildView(
AllocateLoginBigUserView(users[1], false /*is_primary*/));
// Set |spacing_middle|.
AddDisplayLayoutAction(base::BindRepeating(
&LockContentsView::SetLowDensitySpacing, base::Unretained(this),
spacing_middle, opt_secondary_big_view_,
kLowDensityDistanceBetweenUsersInLandscapeDp,
kLowDensityDistanceBetweenUsersInPortraitDp));
}
}
void LockContentsView::CreateMediumDensityLayout(
const std::vector<LoginUserInfo>& users,
std::unique_ptr<LoginBigUserView> primary_big_view) {
// Here is a diagram of this layout:
//
// a A x B y b
//
// a, A: spacing_left
// x: primary_big_view_
// B: spacing_middle
// y: users_list_
// b: spacing_right
//
// A and B are fixed-width spaces; a and b are flexible space that consume any
// additional width.
//
// A and B are the reason for custom layout; no layout manager currently
// supports a fixed-width view that can shrink, but not grow (ie, bounds from
// [0,x]). Custom layout logic is used instead, which is contained inside of
// the AddDisplayLayoutAction call below.
// Construct and add views as described above.
auto* spacing_left =
main_view_->AddChildView(std::make_unique<NonAccessibleView>());
primary_big_view_ = main_view_->AddChildView(std::move(primary_big_view));
auto* spacing_middle =
main_view_->AddChildView(std::make_unique<NonAccessibleView>());
users_list_ = main_view_->AddChildView(
BuildScrollableUsersListView(users, LoginDisplayStyle::kSmall));
auto* spacing_right =
main_view_->AddChildView(std::make_unique<NonAccessibleView>());
// Set width for the |spacing_*| views.
AddDisplayLayoutAction(base::BindRepeating(
[](views::View* host_view, views::View* big_user_view,
views::View* users_list, views::View* spacing_left,
views::View* spacing_middle, views::View* spacing_right,
bool landscape) {
int total_width = host_view->GetPreferredSize().width();
int available_width =
total_width - (big_user_view->GetPreferredSize().width() +
users_list->GetPreferredSize().width());
int left_max_fixed_width =
landscape ? kMediumDensityMarginLeftOfAuthUserLandscapeDp
: kMediumDensityMarginLeftOfAuthUserPortraitDp;
int right_max_fixed_width =
landscape ? kMediumDensityDistanceBetweenAuthUserAndUsersLandscapeDp
: kMediumDensityDistanceBetweenAuthUserAndUsersPortraitDp;
int left_flex_weight = landscape ? 1 : 2;
int right_flex_weight = 1;
MediumViewLayout medium_layout(
available_width, left_flex_weight, left_max_fixed_width,
right_max_fixed_width, right_flex_weight);
SetPreferredWidthForView(
spacing_left,
medium_layout.left_flex_width + medium_layout.left_fixed_width);
SetPreferredWidthForView(spacing_middle,
medium_layout.right_fixed_width);
SetPreferredWidthForView(spacing_right, medium_layout.right_flex_width);
},
this, primary_big_view_, users_list_, spacing_left, spacing_middle,
spacing_right));
}
void LockContentsView::CreateHighDensityLayout(
const std::vector<LoginUserInfo>& users,
views::BoxLayout* main_layout,
std::unique_ptr<LoginBigUserView> primary_big_view) {
// Insert spacing before the auth view.
auto* fill = main_view_->AddChildView(std::make_unique<NonAccessibleView>());
main_layout->SetFlexForView(fill, 1);
primary_big_view_ = main_view_->AddChildView(std::move(primary_big_view));
// Insert spacing after the auth view.
fill = main_view_->AddChildView(std::make_unique<NonAccessibleView>());
main_layout->SetFlexForView(fill, 1);
users_list_ = main_view_->AddChildView(
BuildScrollableUsersListView(users, LoginDisplayStyle::kExtraSmall));
// User list size may change after a display metric change.
AddDisplayLayoutAction(base::BindRepeating(
[](views::View* view, bool landscape) { view->SizeToPreferredSize(); },
users_list_));
}
void LockContentsView::DoLayout() {
const display::Display& display =
display::Screen::GetScreen()->GetDisplayNearestWindow(
GetWidget()->GetNativeWindow());
// Set preferred size before running layout actions, as layout actions may
// depend on the preferred size to determine layout.
gfx::Size preferred_size = display.size();
preferred_size.set_height(preferred_size.height() -
keyboard::KeyboardUIController::Get()
->GetWorkspaceOccludedBoundsInScreen()
.height());
SetPreferredSize(preferred_size);
bool landscape = login_views_utils::ShouldShowLandscape(GetWidget());
for (auto& action : layout_actions_)
action.Run(landscape);
// SizeToPreferredSize will call Layout().
SizeToPreferredSize();
}
void LockContentsView::LayoutTopHeader() {
int preferred_width = system_info_->GetPreferredSize().width() +
note_action_->GetPreferredSize().width();
int preferred_height = std::max(system_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::LayoutBottomStatusIndicator() {
bottom_status_indicator_->SizeToPreferredSize();
// Position the warning indicator in the middle above the shelf.
bottom_status_indicator_->SetPosition(
GetLocalBounds().bottom_center() -
gfx::Vector2d(bottom_status_indicator_->width() / 2,
ShelfConfig::Get()->shelf_size() +
kBottomStatusIndicatorBottomMarginDp +
bottom_status_indicator_->height()));
// If the management bubble is currently displayed, we need to re-layout it as
// the bottom status indicator is its anchor view.
if (management_bubble_->GetVisible())
management_bubble_->Layout();
}
void LockContentsView::LayoutUserAddingScreenIndicator() {
if (Shell::Get()->session_controller()->GetSessionState() !=
session_manager::SessionState::LOGIN_SECONDARY)
return;
// The primary big view may not be ready yet.
if (!primary_big_view_)
return;
user_adding_screen_indicator_->SizeToPreferredSize();
// The element is placed at the middle of the screen horizontally. It is
// placed kDistanceFromBottomOfIndicatorToUserIconDp above the user icon.
// However, if the screen is too small, it is placed
// kMinDistanceFromTopOfScreenToIndicatorDp from top of screen.
int y =
std::max(kMinDistanceFromTopOfScreenToIndicatorDp,
primary_big_view_->y() -
user_adding_screen_indicator_->GetPreferredSize().height() -
kDistanceFromBottomOfIndicatorToUserIconDp);
gfx::Point position(
bounds().width() / 2 -
user_adding_screen_indicator_->GetPreferredSize().width() / 2,
y);
user_adding_screen_indicator_->SetPosition(position);
}
void LockContentsView::LayoutPublicSessionView() {
gfx::Rect bounds = GetContentsBounds();
bounds.ClampToCenteredSize(expanded_view_->GetPreferredSize());
expanded_view_->SetBoundsRect(bounds);
}
void LockContentsView::AddDisplayLayoutAction(
const DisplayLayoutAction& layout_action) {
layout_action.Run(login_views_utils::ShouldShowLandscape(GetWidget()));
layout_actions_.push_back(layout_action);
}
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,
bool display_error_messages) {
if (auth_success) {
if (auth_error_bubble_->GetVisible())
auth_error_bubble_->Hide();
if (detachable_base_error_bubble_->GetVisible())
detachable_base_error_bubble_->Hide();
// 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);
}
// Times a password was incorrectly entered until user succeeds.
Shell::Get()->metrics()->login_metrics_recorder()->RecordNumLoginAttempts(
true /*success*/, &unlock_attempt_);
} else {
++unlock_attempt_;
if (display_error_messages)
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);
auto capture_animation_state_pre_layout = [&](LoginBigUserView* view) {
if (!view)
return;
if (view->auth_user())
view->auth_user()->CaptureStateForAnimationPreLayout();
};
auto enable_auth = [&](LoginBigUserView* view) {
DCHECK(view);
if (view->auth_user()) {
UserState* state = FindStateForUser(
view->auth_user()->current_user().basic_user_info.account_id);
uint32_t to_update_auth;
LoginAuthUserView::AuthMethodsMetadata auth_metadata;
if (state->time_until_tpm_unlock.has_value()) {
// TPM is locked
to_update_auth = LoginAuthUserView::AUTH_DISABLED_TPM_LOCKED;
auth_metadata.time_until_tpm_unlock =
state->time_until_tpm_unlock.value();
} else 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 if (state->show_challenge_response_auth) {
// Currently the challenge-response authentication can't be combined
// with the password or PIN based one.
to_update_auth = LoginAuthUserView::AUTH_CHALLENGE_RESPONSE;
} else {
to_update_auth = LoginAuthUserView::AUTH_PASSWORD;
// Need to check |GetKeyboardControllerForView| as the keyboard may be
// visible, but the keyboard is in a different root window or the view
// has not been added to the widget. In these cases, the keyboard does
// not interfere with PIN entry.
auth_metadata.virtual_keyboard_visible =
GetKeyboardControllerForView() ? keyboard_shown_ : false;
auth_metadata.show_pinpad_for_pw = state->show_pin_pad_for_password;
auth_metadata.autosubmit_pin_length = state->autosubmit_pin_length;
if (state->show_pin)
to_update_auth |= LoginAuthUserView::AUTH_PIN;
if (state->enable_tap_auth)
to_update_auth |= LoginAuthUserView::AUTH_TAP;
if (state->fingerprint_state != FingerprintState::UNAVAILABLE)
to_update_auth |= LoginAuthUserView::AUTH_FINGERPRINT;
}
view->auth_user()->SetAuthMethods(to_update_auth, auth_metadata);
} else if (view->public_account()) {
view->public_account()->SetAuthEnabled(true /*enabled*/, animate);
}
};
auto disable_auth = [&](LoginBigUserView* view) {
if (!view)
return;
if (view->auth_user()) {
view->auth_user()->SetAuthMethods(LoginAuthUserView::AUTH_NONE);
} else if (view->public_account()) {
view->public_account()->SetAuthEnabled(false /*enabled*/, animate);
}
};
auto apply_animation_post_layout = [&](LoginBigUserView* view) {
if (!view)
return;
if (view->auth_user())
view->auth_user()->ApplyAnimationPostLayout(animate);
};
// The high-level layout flow:
capture_animation_state_pre_layout(to_update);
capture_animation_state_pre_layout(opt_to_hide);
enable_auth(to_update);
disable_auth(opt_to_hide);
Layout();
apply_animation_post_layout(to_update);
apply_animation_post_layout(opt_to_hide);
}
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);
LoginUserInfo previous_big_user = primary_big_view_->GetCurrentUser();
LoginUserInfo new_big_user = view->current_user();
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);
}
void LockContentsView::OnBigUserChanged() {
const LoginUserInfo& big_user = CurrentBigUserView()->GetCurrentUser();
const AccountId big_user_account_id = big_user.basic_user_info.account_id;
CurrentBigUserView()->RequestFocus();
Shell::Get()->login_screen_controller()->OnFocusPod(big_user_account_id);
UpdateEasyUnlockIconForUser(big_user_account_id);
// http://crbug/866790: After Supervised Users are deprecated, remove this.
if (big_user.basic_user_info.type == user_manager::USER_TYPE_SUPERVISED) {
base::string16 message = l10n_util::GetStringUTF16(
IDS_ASH_LOGIN_POD_LEGACY_SUPERVISED_EXPIRATION_WARNING);
// Shows supervised user deprecation message as a persistent error bubble.
supervised_user_deprecation_bubble_->SetTextContent(message);
supervised_user_deprecation_bubble_->SetAnchorView(
CurrentBigUserView()->auth_user()->GetActiveInputView());
supervised_user_deprecation_bubble_->Show();
} else if (supervised_user_deprecation_bubble_->GetVisible()) {
supervised_user_deprecation_bubble_->Hide();
}
// 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_->GetVisible())
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(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_ || !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(
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);
ImeControllerImpl* 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 in clamshell mode.
if (ime_controller->available_imes().size() > 1 && !IsTabletMode()) {
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;
}
auto label = std::make_unique<views::StyledLabel>();
label->SetText(error_text);
MakeSectionBold(label.get(), error_text, bold_start, bold_length);
label->SetAutoColorReadabilityEnabled(false);
auto learn_more_button = std::make_unique<SystemLabelButton>(
base::BindRepeating(&LockContentsView::LearnMoreButtonPressed,
base::Unretained(this)),
l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE),
SystemLabelButton::DisplayType::DEFAULT, /*multiline*/ true);
auto container = std::make_unique<NonAccessibleView>(kAuthErrorContainerName);
auto* container_layout =
container->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
kBubbleBetweenChildSpacingDp));
container_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
container->AddChildView(std::move(label));
container->AddChildView(std::move(learn_more_button));
auth_error_bubble_->SetAnchorView(
big_view->auth_user()->GetActiveInputView());
auth_error_bubble_->SetContent(container.release());
auth_error_bubble_->SetAccessibleName(error_text);
auth_error_bubble_->Show();
}
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);
DCHECK(state->easy_unlock_state);
if (!state->easy_unlock_state->tooltip.empty()) {
tooltip_bubble_->SetAnchorView(big_view->auth_user()->GetActiveInputView());
tooltip_bubble_->SetText(state->easy_unlock_state->tooltip);
tooltip_bubble_->Show();
}
}
void LockContentsView::OnEasyUnlockIconTapped() {
UserState* state = FindStateForUser(
CurrentBigUserView()->GetCurrentUser().basic_user_info.account_id);
DCHECK(state);
DCHECK(state->easy_unlock_state);
if (state->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.
OnTapToUnlockEnabledForUserChanged(user, false /*enabled*/);
}
}
void LockContentsView::OnParentAccessValidationFinished(
const AccountId& account_id,
bool access_granted) {
LockContentsView::UserState* state = FindStateForUser(account_id);
Shell::Get()->login_screen_controller()->ShowParentAccessButton(
state && state->disable_auth && !access_granted);
}
keyboard::KeyboardUIController* LockContentsView::GetKeyboardControllerForView()
const {
return GetWidget() ? GetKeyboardControllerForWidget(GetWidget()) : nullptr;
}
void LockContentsView::OnPublicAccountTapped(bool is_primary) {
const LoginBigUserView* user = CurrentBigUserView();
// If the pod should not show an expanded view, tapping on it will launch
// Public Session immediately.
if (!user->GetCurrentUser().public_account_info->show_expanded_view) {
std::string default_input_method;
for (const auto& keyboard :
user->GetCurrentUser().public_account_info->keyboard_layouts) {
if (keyboard.selected) {
default_input_method = keyboard.ime_id;
break;
}
}
Shell::Get()->login_screen_controller()->LaunchPublicSession(
user->GetCurrentUser().basic_user_info.account_id,
user->GetCurrentUser().public_account_info->default_locale,
default_input_method);
return;
}
// 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(user->GetCurrentUser());
SetDisplayStyle(DisplayStyle::kExclusivePublicAccountExpandedView);
}
void LockContentsView::LearnMoreButtonPressed() {
Shell::Get()->login_screen_controller()->ShowAccountAccessHelpApp(
GetWidget()->GetNativeWindow());
auth_error_bubble_->Hide();
}
std::unique_ptr<LoginBigUserView> LockContentsView::AllocateLoginBigUserView(
const LoginUserInfo& 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 std::make_unique<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);
}
std::unique_ptr<ScrollableUsersListView>
LockContentsView::BuildScrollableUsersListView(
const std::vector<LoginUserInfo>& users,
LoginDisplayStyle display_style) {
auto user_list_view = std::make_unique<ScrollableUsersListView>(
users,
base::BindRepeating(&LockContentsView::SwapToBigUser,
base::Unretained(this)),
display_style);
user_list_view->ClipHeightTo(user_list_view->contents()->size().height(),
size().height());
return user_list_view;
}
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);
bottom_status_indicator_->SetVisible(!show_expanded_view);
Layout();
}
bool LockContentsView::OnKeyPressed(const ui::KeyEvent& event) {
switch (event.key_code()) {
case ui::VKEY_RIGHT:
FocusNextUser();
return true;
case ui::VKEY_LEFT:
FocusPreviousUser();
return true;
default:
return false;
}
}
void LockContentsView::RegisterAccelerators() {
for (size_t i = 0; i < kLoginAcceleratorDataLength; ++i) {
if (!kLoginAcceleratorData[i].global)
continue;
if ((screen_type_ == LockScreen::ScreenType::kLogin) &&
!(kLoginAcceleratorData[i].scope & kScopeLogin)) {
continue;
}
if ((screen_type_ == LockScreen::ScreenType::kLock) &&
!(kLoginAcceleratorData[i].scope & kScopeLock)) {
continue;
}
// Show reset conflicts with rotate screen when --ash-dev-shortcuts is
// passed. Favor --ash-dev-shortcuts since that is explicitly added.
if (kLoginAcceleratorData[i].action ==
LoginAcceleratorAction::kShowResetScreen &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAshDeveloperShortcuts)) {
continue;
}
accel_map_[ui::Accelerator(kLoginAcceleratorData[i].keycode,
kLoginAcceleratorData[i].modifiers)] =
kLoginAcceleratorData[i].action;
}
// Register the accelerators.
AcceleratorControllerImpl* controller =
Shell::Get()->accelerator_controller();
for (const auto& item : accel_map_)
controller->Register({item.first}, this);
}
void LockContentsView::PerformAction(LoginAcceleratorAction action) {
if (action == LoginAcceleratorAction::kToggleSystemInfo) {
ToggleSystemInfo();
return;
}
// Do not allow accelerator action when system modal window is open except
// `kShowFeedback` which opens feedback tool on top of system modal.
if (!Shell::IsSystemModalWindowOpen() ||
action == LoginAcceleratorAction::kShowFeedback)
Shell::Get()->login_screen_controller()->HandleAccelerator(action);
}
bool LockContentsView::GetSystemInfoVisibility() const {
if (enable_system_info_enforced_.has_value()) {
return enable_system_info_enforced_.value();
} else {
return enable_system_info_if_possible_;
}
}
void LockContentsView::UpdateBottomStatusIndicatorVisibility() {
bool visible = bottom_status_indicator_status_ ==
BottomIndicatorState::kAdbSideLoadingEnabled ||
(bottom_status_indicator_status_ ==
BottomIndicatorState::kManagedDevice &&
!extension_ui_visible_);
bottom_status_indicator_->SetVisible(visible);
}
void LockContentsView::OnBottomStatusIndicatorTapped() {
if (bottom_status_indicator_status_ != BottomIndicatorState::kManagedDevice)
return;
management_bubble_->Show();
}
BEGIN_METADATA(LockContentsView, NonAccessibleView)
END_METADATA
} // namespace ash