| // Copyright 2022 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/system/phonehub/camera_roll_thumbnail.h" |
| |
| #include "ash/constants/ash_features.h" |
| #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/ash_color_provider.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "chromeos/ash/components/phonehub/camera_roll_item.h" |
| #include "chromeos/ash/components/phonehub/fake_camera_roll_manager.h" |
| #include "chromeos/ash/components/phonehub/fake_user_action_recorder.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/class_property.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/compositor/property_change_reason.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/accessibility/accessibility_paint_checks.h" |
| #include "ui/views/controls/button/menu_button.h" |
| #include "ui/views/style/platform_style.h" |
| #include "ui/views/test/views_test_base.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget_utils.h" |
| |
| namespace ash { |
| |
| constexpr int kRectWidthInDip = 120; |
| constexpr int kRectHeightInDip = 70; |
| constexpr gfx::Size kExpectedCameraRollThumbnailBorderSize(74, 74); |
| constexpr gfx::Point kExpectedCameraRollThumbnailVideoCircleOrigin(37, 37); |
| constexpr int kExpectedCameraRollThumbnailVideoCircleRadius = 16; |
| constexpr gfx::Point kExpectedCameraRollThumbnailVideoIconOrigin(27, 27); |
| constexpr int kExpectedCameraRollThumbnailVideoIconSize = 20; |
| |
| class CameraRollThumbnailForTest : public CameraRollThumbnail { |
| public: |
| CameraRollThumbnailForTest(const phonehub::CameraRollItem& test_item, |
| phonehub::CameraRollManager* camera_roll_manager, |
| phonehub::UserActionRecorder* user_action_recorder) |
| : CameraRollThumbnail(/* index= */ 1, |
| test_item, |
| camera_roll_manager, |
| user_action_recorder) {} |
| ~CameraRollThumbnailForTest() override = default; |
| }; |
| |
| class CameraRollThumbnailTest : public views::ViewsTestBase { |
| public: |
| CameraRollThumbnailTest() |
| : views::ViewsTestBase( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| ~CameraRollThumbnailTest() override = default; |
| |
| // ViewTestBase: |
| void SetUp() override { |
| views::ViewsTestBase::SetUp(); |
| fake_camera_roll_manager_ = |
| std::make_unique<phonehub::FakeCameraRollManager>(); |
| fake_user_action_recorder_ = |
| std::make_unique<phonehub::FakeUserActionRecorder>(); |
| CreateWidget(); |
| generator_ = |
| std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_)); |
| } |
| |
| void TearDown() override { |
| camera_roll_thumbnail_.reset(); |
| fake_camera_roll_manager_.reset(); |
| fake_user_action_recorder_.reset(); |
| generator_.reset(); |
| views::ViewsTestBase::TearDown(); |
| } |
| |
| protected: |
| views::Widget* widget() { return widget_; } |
| ui::test::EventGenerator* generator() { return generator_.get(); } |
| |
| CameraRollThumbnail* camera_roll_thumbnail() const { |
| return camera_roll_thumbnail_.get(); |
| } |
| |
| phonehub::FakeCameraRollManager* fake_camera_roll_manager() { |
| return fake_camera_roll_manager_.get(); |
| } |
| |
| void CreateWidget() { |
| DCHECK(!widget_); |
| widget_ = new views::Widget; |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| params.bounds = gfx::Rect(0, 0, 200, 200); |
| widget_->Init(std::move(params)); |
| } |
| |
| void SetUpCameraRollThumbnailForTest(bool is_video) { |
| auto thumbnail_button = std::make_unique<CameraRollThumbnailForTest>( |
| CreateCameraRollItemWithType(is_video), fake_camera_roll_manager_.get(), |
| fake_user_action_recorder_.get()); |
| camera_roll_thumbnail_ = std::unique_ptr<CameraRollThumbnailForTest>( |
| widget_->SetContentsView(std::move(thumbnail_button))); |
| camera_roll_thumbnail_->SetBoundsRect( |
| gfx::Rect(0, 0, kRectWidthInDip, kRectHeightInDip)); |
| // Accessible name needed to pass accessibility paint checks. Size and |
| // manager index don't matter but still need to be set. |
| const std::u16string accessible_name = l10n_util::GetStringFUTF16( |
| IDS_ASH_PHONE_HUB_CAMERA_ROLL_THUMBNAIL_ACCESSIBLE_NAME, |
| base::NumberToString16(/* index= */ 1), |
| base::NumberToString16(/* camera_roll_manager_size= */ 1)); |
| camera_roll_thumbnail_->SetAccessibleName(accessible_name); |
| widget_->Show(); |
| } |
| |
| SkBitmap CreateExpectedThumbnail(bool is_video) { |
| gfx::Canvas expected(gfx::Size(kRectWidthInDip, kRectHeightInDip), |
| /* image_scale= */ 1.0f, /* is_opaque= */ true); |
| expected.DrawColor(camera_roll_thumbnail()->GetColorProvider()->GetColor( |
| kColorAshControlBackgroundColorInactive)); |
| |
| gfx::ImageSkia test_image = CreateTestThumbnail().AsImageSkia(); |
| expected.DrawImageInt( |
| test_image, 0, 0, test_image.width(), test_image.height(), 0, 0, |
| kExpectedCameraRollThumbnailBorderSize.width(), |
| kExpectedCameraRollThumbnailBorderSize.height(), false); |
| |
| if (is_video) { |
| auto* provider = AshColorProvider::Get(); |
| cc::PaintFlags flags; |
| flags.setAntiAlias(true); |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| flags.setColor(camera_roll_thumbnail()->GetColorProvider()->GetColor( |
| kColorAshShieldAndBase80)); |
| expected.DrawCircle(kExpectedCameraRollThumbnailVideoCircleOrigin, |
| kExpectedCameraRollThumbnailVideoCircleRadius, flags); |
| expected.DrawImageInt( |
| CreateVectorIcon( |
| kPhoneHubCameraRollItemVideoIcon, |
| kExpectedCameraRollThumbnailVideoIconSize, |
| provider->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kIconColorPrimary)), |
| kExpectedCameraRollThumbnailVideoIconOrigin.x(), |
| kExpectedCameraRollThumbnailVideoIconOrigin.y()); |
| } |
| |
| return expected.GetBitmap(); |
| } |
| |
| private: |
| const phonehub::CameraRollItem CreateCameraRollItemWithType(bool is_video) { |
| phonehub::proto::CameraRollItemMetadata metadata; |
| metadata.set_key("key"); |
| metadata.set_last_modified_millis(1577865600); |
| metadata.set_file_size_bytes(123456); |
| |
| if (is_video) { |
| metadata.set_mime_type("video/mp4"); |
| metadata.set_file_name("fake_video.mp4"); |
| } else { |
| metadata.set_mime_type("image/png"); |
| metadata.set_file_name("fake_image.png"); |
| } |
| |
| return phonehub::CameraRollItem(metadata, CreateTestThumbnail()); |
| } |
| |
| const gfx::Image CreateTestThumbnail() { |
| SkBitmap bitmap; |
| bitmap.allocN32Pixels(1, 1); |
| return gfx::Image::CreateFrom1xBitmap(bitmap); |
| } |
| |
| // This is required in order for the context to find color provider |
| AshColorProvider color_provider_; |
| raw_ptr<views::Widget, DanglingUntriaged> widget_ = nullptr; |
| std::unique_ptr<CameraRollThumbnail> camera_roll_thumbnail_; |
| std::unique_ptr<phonehub::FakeUserActionRecorder> fake_user_action_recorder_; |
| std::unique_ptr<phonehub::FakeCameraRollManager> fake_camera_roll_manager_; |
| std::unique_ptr<ui::test::EventGenerator> generator_; |
| }; |
| |
| TEST_F(CameraRollThumbnailTest, ViewLayout) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ false); |
| |
| EXPECT_EQ(camera_roll_thumbnail()->GetFocusBehavior(), |
| CameraRollThumbnail::FocusBehavior::ALWAYS); |
| EXPECT_STREQ("CameraRollThumbnail", camera_roll_thumbnail()->GetClassName()); |
| } |
| |
| TEST_F(CameraRollThumbnailTest, ImageThumbnail) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ false); |
| EXPECT_EQ(camera_roll_thumbnail()->GetFocusBehavior(), |
| CameraRollThumbnail::FocusBehavior::ALWAYS); |
| |
| gfx::Canvas placeholder(gfx::Size(kRectWidthInDip, kRectHeightInDip), |
| /* image_scale= */ 1.0f, /* is_opaque= */ true); |
| gfx::Canvas* ptr_placeholder; |
| ptr_placeholder = &placeholder; |
| camera_roll_thumbnail()->PaintButtonContents(ptr_placeholder); |
| |
| EXPECT_TRUE( |
| gfx::test::AreBitmapsEqual(CreateExpectedThumbnail(/* is_video= */ false), |
| ptr_placeholder->GetBitmap())); |
| } |
| |
| TEST_F(CameraRollThumbnailTest, VideoThumbnail) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ true); |
| EXPECT_EQ(camera_roll_thumbnail()->GetFocusBehavior(), |
| CameraRollThumbnail::FocusBehavior::ALWAYS); |
| |
| gfx::Canvas placeholder(gfx::Size(kRectWidthInDip, kRectHeightInDip), |
| /* image_scale= */ 1.0f, /* is_opaque= */ true); |
| gfx::Canvas* ptr_placeholder; |
| ptr_placeholder = &placeholder; |
| camera_roll_thumbnail()->PaintButtonContents(ptr_placeholder); |
| |
| EXPECT_TRUE( |
| gfx::test::AreBitmapsEqual(CreateExpectedThumbnail(/* is_video= */ true), |
| ptr_placeholder->GetBitmap())); |
| } |
| |
| TEST_F(CameraRollThumbnailTest, LeftClickDownload) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ false); |
| EXPECT_TRUE(!camera_roll_thumbnail()->menu_model_); |
| |
| // Left click button |
| generator()->MoveMouseTo( |
| camera_roll_thumbnail()->GetBoundsInScreen().CenterPoint()); |
| generator()->ClickLeftButton(); |
| |
| // Menu model of type CameraRollMenuModel is not created |
| EXPECT_FALSE(dynamic_cast<CameraRollMenuModel*>( |
| camera_roll_thumbnail()->menu_model_.get()) != nullptr); |
| |
| // Download was triggered |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 1); |
| } |
| |
| TEST_F(CameraRollThumbnailTest, LeftClickDownloadCantFollowupDownload) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ false); |
| |
| // Left click button |
| generator()->MoveMouseTo( |
| camera_roll_thumbnail()->GetBoundsInScreen().CenterPoint()); |
| generator()->ClickLeftButton(); |
| |
| // Menu model of type CameraRollMenuModel is not created |
| EXPECT_FALSE(dynamic_cast<CameraRollMenuModel*>( |
| camera_roll_thumbnail()->menu_model_.get()) != nullptr); |
| |
| // Download was triggered |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 1); |
| |
| // Immediately try to download again |
| generator()->ClickLeftButton(); |
| |
| // Still only 1 download occurred due to timer running |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 1); |
| } |
| |
| TEST_F(CameraRollThumbnailTest, LeftClickDownloadWithBackoff) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ false); |
| |
| // Left click button |
| generator()->MoveMouseTo( |
| camera_roll_thumbnail()->GetBoundsInScreen().CenterPoint()); |
| generator()->ClickLeftButton(); |
| |
| // Menu model of type CameraRollMenuModel is not created |
| EXPECT_FALSE(dynamic_cast<CameraRollMenuModel*>( |
| camera_roll_thumbnail()->menu_model_.get()) != nullptr); |
| |
| // Download was triggered |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 1); |
| |
| // Wait for enough time to pass to be able to download again |
| task_environment()->FastForwardBy( |
| features::kPhoneHubCameraRollThrottleInterval.Get()); |
| generator()->ClickLeftButton(); |
| |
| // Menu model of type CameraRollMenuModel is not created |
| EXPECT_FALSE(dynamic_cast<CameraRollMenuModel*>( |
| camera_roll_thumbnail()->menu_model_.get()) != nullptr); |
| |
| // Second download occurs |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 2); |
| } |
| |
| TEST_F(CameraRollThumbnailTest, RightClickOpenMenu) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ false); |
| EXPECT_TRUE(!camera_roll_thumbnail()->menu_model_); |
| |
| // Right click button |
| generator()->MoveMouseTo( |
| camera_roll_thumbnail()->GetBoundsInScreen().CenterPoint()); |
| generator()->ClickRightButton(); |
| |
| // Download was not triggered |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 0); |
| |
| // Menu model of type CameraRollMenuModel is created |
| EXPECT_TRUE(dynamic_cast<CameraRollMenuModel*>( |
| camera_roll_thumbnail()->menu_model_.get()) != nullptr); |
| |
| // Triggering menu item callback |
| camera_roll_thumbnail()->menu_model_.get()->ExecuteCommand( |
| CameraRollMenuModel::CommandID::COMMAND_DOWNLOAD, 0); |
| |
| // Download was triggered |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 1); |
| } |
| |
| TEST_F(CameraRollThumbnailTest, ThrottleTimerDoesntBlockRightClickMenu) { |
| SetUpCameraRollThumbnailForTest(/* is_video= */ false); |
| |
| // Left click button |
| generator()->MoveMouseTo( |
| camera_roll_thumbnail()->GetBoundsInScreen().CenterPoint()); |
| generator()->ClickLeftButton(); |
| |
| // Menu model of type CameraRollMenuModel is not created |
| EXPECT_FALSE(dynamic_cast<CameraRollMenuModel*>( |
| camera_roll_thumbnail()->menu_model_.get()) != nullptr); |
| |
| // Download was triggered |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 1); |
| |
| // Immediately try to download again |
| generator()->ClickLeftButton(); |
| |
| // Still only 1 download occurred due to timer running |
| EXPECT_EQ(fake_camera_roll_manager()->GetDownloadRequestCount(), 1); |
| |
| // Right click button |
| generator()->MoveMouseTo( |
| camera_roll_thumbnail()->GetBoundsInScreen().CenterPoint()); |
| generator()->ClickRightButton(); |
| |
| // Menu model of type CameraRollMenuModel is created |
| EXPECT_TRUE(dynamic_cast<CameraRollMenuModel*>( |
| camera_roll_thumbnail()->menu_model_.get()) != nullptr); |
| } |
| |
| } // namespace ash |