blob: 1ef83da682c4c984f2727ff79e49f77a2506199d [file]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/login/ui/disabled_auth_message_view.h"
#include <memory>
#include <string>
#include <string_view>
#include "ash/public/cpp/login_types.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/raw_ptr.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/view.h"
namespace ash {
namespace {
constexpr int kVerticalBorderDp = 16;
constexpr int kHorizontalBorderDp = 16;
constexpr int kChildrenSpacingDp = 4;
constexpr int kTimeWidthDp = 204;
constexpr int kMultiprofileWidthDp = 304;
constexpr int kIconSizeDp = 24;
constexpr int kTitleFontSizeDeltaDp = 3;
constexpr int kContentsFontSizeDeltaDp = -1;
constexpr int kRoundedCornerRadiusDp = 8;
// The content needed to render the disabled auth message view.
struct LockScreenMessage {
std::u16string title;
std::u16string content;
ui::ImageModel icon;
};
// Returns the message used when the device was locked due to a time window
// limit.
LockScreenMessage GetWindowLimitMessage(const base::Time& unlock_time,
bool use_24hour_clock) {
LockScreenMessage message;
message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_FOR_BED_MESSAGE);
base::Time local_midnight = base::Time::Now().LocalMidnight();
std::u16string time_to_display;
if (use_24hour_clock) {
time_to_display = base::TimeFormatTimeOfDayWithHourClockType(
unlock_time, base::k24HourClock, base::kDropAmPm);
} else {
time_to_display = base::TimeFormatTimeOfDayWithHourClockType(
unlock_time, base::k12HourClock, base::kKeepAmPm);
}
if (unlock_time < local_midnight + base::Days(1)) {
// Unlock time is today.
message.content = l10n_util::GetStringFUTF16(
IDS_ASH_LOGIN_COME_BACK_MESSAGE, time_to_display);
} else if (unlock_time < local_midnight + base::Days(2)) {
// Unlock time is tomorrow.
message.content = l10n_util::GetStringFUTF16(
IDS_ASH_LOGIN_COME_BACK_TOMORROW_MESSAGE, time_to_display);
} else {
message.content = l10n_util::GetStringFUTF16(
IDS_ASH_LOGIN_COME_BACK_DAY_OF_WEEK_MESSAGE,
base::LocalizedTimeFormatWithPattern(unlock_time, "EEEE"),
time_to_display);
}
message.icon = ui::ImageModel::FromVectorIcon(
kLockScreenTimeLimitMoonIcon, kColorAshIconColorPrimary, kIconSizeDp);
return message;
}
// Returns the message used when the device was locked due to a time usage
// limit.
LockScreenMessage GetUsageLimitMessage(const base::TimeDelta& used_time) {
LockScreenMessage message;
// 1 minute is used instead of 0, because the device is used for a few
// milliseconds before locking.
if (used_time < base::Minutes(1)) {
// The device was locked all day.
message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TAKE_BREAK_MESSAGE);
message.content =
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_LOCKED_ALL_DAY_MESSAGE);
} else {
// The usage limit is over.
message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_IS_UP_MESSAGE);
const std::u16string used_time_string = ui::TimeFormat::Detailed(
ui::TimeFormat::Format::FORMAT_DURATION,
ui::TimeFormat::Length::LENGTH_LONG, /*cutoff=*/3, used_time);
message.content = l10n_util::GetStringFUTF16(
IDS_ASH_LOGIN_SCREEN_TIME_USED_MESSAGE, used_time_string);
}
message.icon = ui::ImageModel::FromVectorIcon(
kLockScreenTimeLimitTimerIcon, kColorAshIconColorPrimary, kIconSizeDp);
return message;
}
// Returns the message used when the device was locked due to a time limit
// override.
LockScreenMessage GetOverrideMessage() {
LockScreenMessage message;
message.title =
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_FOR_A_BREAK_MESSAGE);
message.content =
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_MANUAL_LOCK_MESSAGE);
message.icon = ui::ImageModel::FromVectorIcon(
kLockScreenTimeLimitLockIcon, kColorAshIconColorPrimary, kIconSizeDp);
return message;
}
LockScreenMessage GetLockScreenMessage(AuthDisabledReason lock_reason,
const base::Time& unlock_time,
const base::TimeDelta& used_time,
bool use_24hour_clock) {
switch (lock_reason) {
case AuthDisabledReason::kTimeWindowLimit:
return GetWindowLimitMessage(unlock_time, use_24hour_clock);
case AuthDisabledReason::kTimeUsageLimit:
return GetUsageLimitMessage(used_time);
case AuthDisabledReason::kTimeLimitOverride:
return GetOverrideMessage();
default:
NOTREACHED();
}
}
} // namespace
DisabledAuthMessageView::TestApi::TestApi(DisabledAuthMessageView* view)
: view_(view) {}
DisabledAuthMessageView::TestApi::~TestApi() = default;
std::u16string_view
DisabledAuthMessageView::TestApi::GetDisabledAuthMessageContent() const {
return view_->message_contents_->GetText();
}
void DisabledAuthMessageView::TestApi::SetDisabledAuthMessageTitleForTesting(
std::u16string message_title) {
view_->SetAuthDisabledMessage(message_title, u"");
}
DisabledAuthMessageView::DisabledAuthMessageView() {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets::VH(kVerticalBorderDp, kHorizontalBorderDp),
kChildrenSpacingDp));
preferred_width_ = kTimeWidthDp;
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
SetFocusBehavior(FocusBehavior::ALWAYS);
SetBackground(views::CreateThemedRoundedRectBackground(
kColorAshShieldAndBaseOpaque, kRoundedCornerRadiusDp));
// The icon size has to be defined later if the image will be visible.
message_icon_ = AddChildView(std::make_unique<views::ImageView>());
message_icon_->SetImageSize(gfx::Size(kIconSizeDp, kIconSizeDp));
auto decorate_label = [](views::Label* label) {
label->SetSubpixelRenderingEnabled(false);
label->SetAutoColorReadabilityEnabled(false);
label->SetFocusBehavior(FocusBehavior::ALWAYS);
};
message_title_ = AddChildView(std::make_unique<views::Label>(
std::u16string(), views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY));
message_title_->SetFontList(gfx::FontList().Derive(
kTitleFontSizeDeltaDp, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
decorate_label(message_title_);
message_title_->SetEnabledColor(kColorAshTextColorPrimary);
message_contents_ = AddChildView(std::make_unique<views::Label>(
std::u16string(), views::style::CONTEXT_LABEL,
views::style::STYLE_PRIMARY));
message_contents_->SetFontList(gfx::FontList().Derive(
kContentsFontSizeDeltaDp, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL));
decorate_label(message_contents_);
message_contents_->SetMultiLine(true);
message_contents_->SetEnabledColor(kColorAshTextColorPrimary);
GetViewAccessibility().SetRole(ax::mojom::Role::kPane);
UpdateAccessibleName();
message_title_changed_subscription_ = message_title_->AddTextChangedCallback(
base::BindRepeating(&DisabledAuthMessageView::OnMessageTitleChanged,
weak_ptr_factory_.GetWeakPtr()));
}
DisabledAuthMessageView::~DisabledAuthMessageView() = default;
void DisabledAuthMessageView::SetAuthDisabledMessage(
const AuthDisabledData& auth_disabled_data,
bool use_24hour_clock) {
LockScreenMessage message = GetLockScreenMessage(
auth_disabled_data.reason, auth_disabled_data.auth_reenabled_time,
auth_disabled_data.device_used_time, use_24hour_clock);
preferred_width_ = kTimeWidthDp;
message_icon_->SetVisible(true);
CHECK(!message.icon.IsEmpty());
message_icon_->SetImage(message.icon);
message_title_->SetText(message.title);
message_title_->SetEnabledColor(kColorAshTextColorPrimary);
message_contents_->SetText(message.content);
message_contents_->SetEnabledColor(kColorAshTextColorPrimary);
}
void DisabledAuthMessageView::SetAuthDisabledMessage(
const std::u16string& title,
const std::u16string& content) {
preferred_width_ = kMultiprofileWidthDp;
message_icon_->SetVisible(false);
message_title_->SetText(title);
message_contents_->SetText(content);
}
void DisabledAuthMessageView::RequestFocus() {
message_title_->RequestFocus();
}
gfx::Size DisabledAuthMessageView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
int height =
GetLayoutManager()->GetPreferredHeightForWidth(this, preferred_width_);
return gfx::Size(preferred_width_, height);
}
void DisabledAuthMessageView::OnMessageTitleChanged() {
UpdateAccessibleName();
}
void DisabledAuthMessageView::UpdateAccessibleName() {
// Any view which claims to be focusable is expected to have an accessible
// name so that screen readers know what to present to the user when it gains
// focus. In the case of this particular view, `RequestFocus` gives focus to
// the `message_title_` label. As a result, this view is not end-user
// focusable. However, its official focusability will cause the accessibility
// paint checks to fail. If the `message_title_` has text, set the accessible
// name to that text; otherwise set the name explicitly empty to prevent
// the paint check from failing during tests.
if (message_title_->GetText().empty()) {
GetViewAccessibility().SetName(
std::string(), ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
} else {
GetViewAccessibility().SetName(std::u16string(message_title_->GetText()));
}
}
} // namespace ash