0

Implement pin keyboard view

Bug: b:286527724
Change-Id: I6daccfb111fce3f1ddfaccf48c35df34b3e594c2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5482231
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Istvan Nagy <iscsi@google.com>
Cr-Commit-Position: refs/heads/main@{#1306354}
This commit is contained in:
Istvan Nagy
2024-05-27 10:45:16 +00:00
committed by Chromium LUCI CQ
parent dcf7ffb004
commit acdf9bb0e4
7 changed files with 485 additions and 0 deletions

@ -330,6 +330,8 @@ component("ash") {
"assistant/assistant_web_ui_controller.h",
"assistant/assistant_web_view_delegate_impl.cc",
"assistant/assistant_web_view_delegate_impl.h",
"auth/views/pin_keyboard_view.cc",
"auth/views/pin_keyboard_view.h",
"autotest_private_api_utils.cc",
"birch/birch_client.h",
"birch/birch_data_provider.h",
@ -3561,6 +3563,7 @@ test("ash_unittests") {
"assistant/ui/main_stage/ui_element_container_view_unittest.cc",
"assistant/util/deep_link_util_unittest.cc",
"assistant/util/resource_util_unittest.cc",
"auth/views/pin_keyboard_view_unittest.cc",
"birch/birch_icon_cache_unittest.cc",
"birch/birch_item_remover_unittest.cc",
"birch/birch_item_unittest.cc",

@ -5078,6 +5078,12 @@ No devices connected.
Your Chromebook needs to stay on and connected to power during this time. Make sure the charger or adapter cables are completely plugged in, both to your Chromebook and the power outlet. Do not turn off your Chromebook.
</message>
<!-- Authentication strings -->
<message name="IDS_ASH_AUTH_PIN_KEYBOARD" desc="The accessibility text for the authentication pin keyboard.">
Pin keyboard
</message>
<!-- Fast Pair strings -->
<message name="IDS_FAST_PAIR_CONNECTION_ERROR_TITLE" desc="Notification title shown when a there is a Fast Pair connection error.">
Couldn't connect <ph name="NAME">$1<ex>Pixel Buds</ex></ph>

@ -0,0 +1 @@
5d22689c1a50b838ddf6fa03594101e7c9cab0e9

1
ash/auth/OWNERS Normal file

@ -0,0 +1 @@
file://chromeos/ash/components/osauth/OWNERS

@ -0,0 +1,258 @@
// Copyright 2024 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/auth/views/pin_keyboard_view.h"
#include <memory>
#include <string>
#include <string_view>
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/icon_button.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/view.h"
// TODO(b/335543015): This part of code is just for to provide a temporary
// solution to show digits on the icon button while to issue is fixed.
#include "base/strings/string_number_conversions.h"
#include "ui/base/models/image_model.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"
// Temporary includes end.
namespace ash {
namespace {
constexpr int kButtonSize = 48;
constexpr int kFontSize = 32;
constexpr int kButtonsVerticalPadding = 12;
constexpr int kButtonsHorizontalPadding = 16;
constexpr int kPinKeyboardHeightDp =
4 * kButtonSize + 3 * kButtonsVerticalPadding;
constexpr int kPinKeyboardWidthDp =
3 * kButtonSize + 2 * kButtonsHorizontalPadding;
constexpr ui::ColorId kButtonBackgroundColorId =
cros_tokens::kCrosSysSystemBaseElevated;
constexpr ui::ColorId kButtonContentColorId = cros_tokens::kCrosSysOnSurface;
void StyleButton(IconButton* button_ptr) {
button_ptr->SetBackgroundColor(kButtonBackgroundColorId);
button_ptr->SetIconColor(kButtonContentColorId);
}
// TODO(b/335543015): This function should be removed after the issue is fixed.
class DigitImageSource : public gfx::CanvasImageSource {
public:
DigitImageSource(PinKeyboardView* view, int digit)
: gfx::CanvasImageSource(gfx::Size(kFontSize, kFontSize)),
view_(view),
digit_(digit) {}
DigitImageSource(const DigitImageSource&) = delete;
DigitImageSource& operator=(const DigitImageSource&) = delete;
~DigitImageSource() override = default;
void Draw(gfx::Canvas* canvas) override {
SkColor digit_color =
view_->GetColorProvider()->GetColor(kButtonContentColorId);
gfx::FontList font = gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 32,
gfx::Font::Weight::NORMAL);
canvas->DrawStringRectWithFlags(
base::NumberToString16(digit_), font, digit_color,
gfx::Rect(kFontSize, kFontSize), gfx::Canvas::TEXT_ALIGN_CENTER);
}
private:
raw_ptr<PinKeyboardView> view_ = nullptr;
int digit_;
};
} // namespace
//----------------------- PinKeyboardView Test API ------------------------
PinKeyboardView::TestApi::TestApi(PinKeyboardView* view) : view_(view) {
CHECK(view_);
}
PinKeyboardView::TestApi::~TestApi() = default;
views::Button* PinKeyboardView::TestApi::backspace_button() {
return view_->backspace_button_;
}
views::Button* PinKeyboardView::TestApi::digit_button(int digit) {
CHECK(view_->digit_buttons_.contains(digit));
CHECK(view_->digit_buttons_[digit]);
return view_->digit_buttons_[digit];
}
bool PinKeyboardView::TestApi::GetEnabled() {
return view_->GetEnabled();
}
void PinKeyboardView::TestApi::SetEnabled(bool enabled) {
view_->SetEnabled(enabled);
}
void PinKeyboardView::TestApi::AddObserver(
PinKeyboardView::Observer* observer) {
view_->AddObserver(observer);
}
void PinKeyboardView::TestApi::RemoveObserver(
PinKeyboardView::Observer* observer) {
view_->RemoveObserver(observer);
}
PinKeyboardView* PinKeyboardView::TestApi::GetView() {
return view_;
}
//----------------------- PinKeyboardView ------------------------
PinKeyboardView::PinKeyboardView() {
SetAccessibleName(l10n_util::GetStringUTF16(IDS_ASH_AUTH_PIN_KEYBOARD));
// The pin pad is always LTR.
SetFlipCanvasOnPaintForRTLUI(false);
// 3x4 with the following layout
// 1 2 3
// 4 5 6
// 7 8 9
//<- 0
SetLayoutManager(std::make_unique<views::TableLayout>())
->AddColumn(views::LayoutAlignment::kStart,
views::LayoutAlignment::kStart,
views::TableLayout::kFixedSize,
views::TableLayout::ColumnSize::kFixed, kButtonSize, 0)
.AddPaddingColumn(views::TableLayout::kFixedSize,
kButtonsHorizontalPadding)
.AddColumn(views::LayoutAlignment::kStart, views::LayoutAlignment::kStart,
views::TableLayout::kFixedSize,
views::TableLayout::ColumnSize::kFixed, kButtonSize, 0)
.AddPaddingColumn(views::TableLayout::kFixedSize,
kButtonsHorizontalPadding)
.AddColumn(views::LayoutAlignment::kStart, views::LayoutAlignment::kStart,
views::TableLayout::kFixedSize,
views::TableLayout::ColumnSize::kFixed, kButtonSize, 0)
.AddRows(1, 0, kButtonSize)
.AddPaddingRow(0, kButtonsVerticalPadding)
.AddRows(1, 0, kButtonSize)
.AddPaddingRow(0, kButtonsVerticalPadding)
.AddRows(1, 0, kButtonSize)
.AddPaddingRow(0, kButtonsVerticalPadding)
.AddRows(1, 0, kButtonSize);
//----------------------- First row ------------------------
AddDigitButton(1);
AddDigitButton(2);
AddDigitButton(3);
//----------------------- Second row ------------------------
AddDigitButton(4);
AddDigitButton(5);
AddDigitButton(6);
//----------------------- Third row ------------------------
AddDigitButton(7);
AddDigitButton(8);
AddDigitButton(9);
//----------------------- Fourth row ------------------------
backspace_button_ = AddChildView(std::make_unique<IconButton>(
base::BindRepeating(&PinKeyboardView::OnBackspacePressed,
weak_ptr_factory_.GetWeakPtr()),
IconButton::Type::kXLarge, &kLockScreenBackspaceIcon,
IDS_ASH_PIN_KEYBOARD_DELETE_ACCESSIBLE_NAME));
StyleButton(backspace_button_);
AddDigitButton(0);
}
PinKeyboardView::~PinKeyboardView() = default;
gfx::Size PinKeyboardView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
return gfx::Size(kPinKeyboardWidthDp, kPinKeyboardHeightDp);
}
void PinKeyboardView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kKeyboard;
node_data->SetName(GetAccessibleName());
}
void PinKeyboardView::OnDigitButtonPressed(int digit) {
CHECK_GE(digit, 0);
CHECK_LT(digit, 10);
CHECK(GetEnabled());
for (auto& observer : observers_) {
observer.OnDigitButtonPressed(digit);
}
}
void PinKeyboardView::OnBackspacePressed() {
CHECK(GetEnabled());
for (auto& observer : observers_) {
observer.OnBackspacePressed();
}
}
void PinKeyboardView::AddDigitButton(int digit) {
CHECK_GE(digit, 0);
CHECK_LT(digit, 10);
auto* button_ptr = AddChildView(std::make_unique<IconButton>(
base::BindRepeating(&PinKeyboardView::OnDigitButtonPressed,
weak_ptr_factory_.GetWeakPtr(), digit),
IconButton::Type::kXLarge, &kLockScreenBackspaceIcon,
IDS_ASH_PIN_KEYBOARD_DELETE_ACCESSIBLE_NAME));
digit_buttons_[digit] = button_ptr;
StyleButton(button_ptr);
// TODO(b/335543015): This part of the code should be removed after the issue
// is fixed.
ui::ImageModel img = ui::ImageModel::FromImageSkia(
gfx::CanvasImageSource::MakeImageSkia<DigitImageSource>(this, digit));
button_ptr->SetImageModel(IconButton::ButtonState::STATE_NORMAL, img);
// Temporary code end.
}
void PinKeyboardView::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PinKeyboardView::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
BEGIN_METADATA(PinKeyboardView)
END_METADATA
} // namespace ash

@ -0,0 +1,84 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_AUTH_VIEWS_PIN_KEYBOARD_VIEW_H_
#define ASH_AUTH_VIEWS_PIN_KEYBOARD_VIEW_H_
#include "ash/ash_export.h"
#include "ash/style/icon_button.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/view.h"
namespace ash {
class ASH_EXPORT PinKeyboardView : public views::View {
METADATA_HEADER(PinKeyboardView, views::View)
public:
class Observer : public base::CheckedObserver {
public:
virtual void OnDigitButtonPressed(int digit) {}
virtual void OnBackspacePressed() {}
};
class TestApi {
public:
explicit TestApi(PinKeyboardView* view);
~TestApi();
views::Button* backspace_button();
views::Button* digit_button(int digit);
void SetEnabled(bool enabled);
bool GetEnabled();
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
PinKeyboardView* GetView();
private:
const raw_ptr<PinKeyboardView> view_;
};
PinKeyboardView();
PinKeyboardView(const PinKeyboardView&) = delete;
PinKeyboardView& operator=(const PinKeyboardView&) = delete;
~PinKeyboardView() override;
// views::View:
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
private:
void OnDigitButtonPressed(int digit);
void OnBackspacePressed();
void AddDigitButton(int digit);
base::ObserverList<Observer> observers_;
raw_ptr<IconButton> backspace_button_ = nullptr;
base::flat_map<int, raw_ptr<IconButton>> digit_buttons_;
base::WeakPtrFactory<PinKeyboardView> weak_ptr_factory_{this};
};
} // namespace ash
#endif // ASH_AUTH_VIEWS_PIN_KEYBOARD_VIEW_H_

@ -0,0 +1,132 @@
// Copyright 2024 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/auth/views/pin_keyboard_view.h"
#include <memory>
#include <optional>
#include <string>
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/events/event.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/widget/widget.h"
namespace ash {
class PinKeyboardViewUnitTest : public AshTestBase,
public PinKeyboardView::Observer,
public testing::WithParamInterface<int> {
public:
PinKeyboardViewUnitTest() = default;
PinKeyboardViewUnitTest(const PinKeyboardViewUnitTest&) = delete;
PinKeyboardViewUnitTest& operator=(const PinKeyboardViewUnitTest&) = delete;
~PinKeyboardViewUnitTest() override = default;
MOCK_METHOD(void, OnDigitButtonPressed, (int), (override));
MOCK_METHOD(void, OnBackspacePressed, (), (override));
protected:
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
widget_ = CreateFramelessTestWidget();
widget_->SetFullscreen(true);
PinKeyboardView* view =
widget_->SetContentsView(std::make_unique<PinKeyboardView>());
view_test_api_ = std::make_unique<PinKeyboardView::TestApi>(view);
view_test_api_->AddObserver(this);
}
void TearDown() override {
AshTestBase::TearDown();
view_test_api_->RemoveObserver(this);
view_test_api_.reset();
widget_.reset();
}
std::unique_ptr<views::Widget> widget_;
std::unique_ptr<PinKeyboardView::TestApi> view_test_api_;
};
// Testing pin keyboard's backspace button click.
// Verifying the observer called properly.
TEST_F(PinKeyboardViewUnitTest, ClickOnBackspace) {
auto* button_ptr = view_test_api_->backspace_button();
EXPECT_NE(button_ptr, nullptr);
EXPECT_TRUE(view_test_api_->GetEnabled());
EXPECT_CALL(*this, OnBackspacePressed()).Times(1);
LeftClickOn(button_ptr);
}
// Verifying pin keyboard's SetEnabled triggers the
// property callback.
TEST_F(PinKeyboardViewUnitTest, SetEnabledCallback) {
bool enabled_changed = false;
auto subscription =
view_test_api_->GetView()->AddEnabledChangedCallback(base::BindRepeating(
[](bool* enabled_changed) { *enabled_changed = true; },
&enabled_changed));
EXPECT_TRUE(view_test_api_->GetEnabled());
view_test_api_->SetEnabled(false);
EXPECT_TRUE(enabled_changed);
}
// Testing disabled pin keyboard's backspace button click.
// Verifying the observer doesn't called.
TEST_F(PinKeyboardViewUnitTest, ClickOnDisabledBackspace) {
auto* button_ptr = view_test_api_->backspace_button();
EXPECT_NE(button_ptr, nullptr);
EXPECT_TRUE(view_test_api_->GetEnabled());
view_test_api_->SetEnabled(false);
EXPECT_FALSE(view_test_api_->GetEnabled());
EXPECT_CALL(*this, OnBackspacePressed()).Times(0);
LeftClickOn(button_ptr);
}
// Pin keyboard test with parametrized input.
// Verifying click behavior and observer function across various digits.
TEST_P(PinKeyboardViewUnitTest, ClickOnDigit) {
int digit = GetParam();
EXPECT_TRUE(digit >= 0 && digit <= 9);
auto* button_ptr = view_test_api_->digit_button(digit);
EXPECT_NE(button_ptr, nullptr);
EXPECT_TRUE(view_test_api_->GetEnabled());
EXPECT_CALL(*this, OnDigitButtonPressed(digit)).Times(1);
LeftClickOn(button_ptr);
}
// Testing disabled pin keyboard with parametrized input.
// Verifying click behavior doesn't call the observer function across various
// digits.
TEST_P(PinKeyboardViewUnitTest, ClickOnDisabledDigit) {
int digit = GetParam();
EXPECT_TRUE(digit >= 0 && digit <= 9);
auto* button_ptr = view_test_api_->digit_button(digit);
EXPECT_NE(button_ptr, nullptr);
EXPECT_TRUE(view_test_api_->GetEnabled());
view_test_api_->SetEnabled(false);
EXPECT_FALSE(view_test_api_->GetEnabled());
EXPECT_CALL(*this, OnDigitButtonPressed(digit)).Times(0);
LeftClickOn(button_ptr);
}
INSTANTIATE_TEST_SUITE_P(, PinKeyboardViewUnitTest, ::testing::Range(0, 10));
} // namespace ash