| // Copyright 2020 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/clipboard/clipboard_nudge_controller.h" |
| |
| #include <array> |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/clipboard/clipboard_history.h" |
| #include "ash/clipboard/clipboard_history_controller_impl.h" |
| #include "ash/clipboard/clipboard_nudge.h" |
| #include "ash/clipboard/clipboard_nudge_constants.h" |
| #include "ash/clipboard/test_support/clipboard_history_item_builder.h" |
| #include "ash/constants/notifier_catalogs.h" |
| #include "ash/public/cpp/system/anchored_nudge_data.h" |
| #include "ash/public/cpp/system/anchored_nudge_manager.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/system/toast/anchored_nudge_manager_impl.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "base/time/time.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "chromeos/crosapi/mojom/clipboard_history.mojom.h" |
| #include "ui/base/clipboard/clipboard_data.h" |
| #include "ui/base/clipboard/clipboard_non_backed.h" |
| #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animator.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/views/widget/widget_observer.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| using crosapi::mojom::ClipboardHistoryControllerShowSource::kAccelerator; |
| |
| constexpr char kClipboardNudgeId[] = "ClipboardContextualNudge"; |
| |
| // The array of all clipboard nudge types. |
| constexpr std::array<ClipboardNudgeType, ClipboardNudgeType::kMax + 1> |
| kAllClipboardNudgeTypes = {ClipboardNudgeType::kOnboardingNudge, |
| ClipboardNudgeType::kZeroStateNudge, |
| ClipboardNudgeType::kScreenshotNotificationNudge, |
| ClipboardNudgeType::kDuplicateCopyNudge}; |
| |
| } // namespace |
| |
| class ClipboardNudgeControllerTestBase : public AshTestBase { |
| public: |
| ClipboardNudgeControllerTestBase(bool enable_system_nudge_migration) |
| : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME), |
| enable_system_nudge_migration_(enable_system_nudge_migration) { |
| scoped_feature_list_.InitWithFeatureState(features::kSystemNudgeMigration, |
| IsSystemNudgeMigrationEnabled()); |
| } |
| |
| base::SimpleTestClock* clock() { return &test_clock_; } |
| |
| // AshTestBase: |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| nudge_controller_ = |
| Shell::Get()->clipboard_history_controller()->nudge_controller(); |
| nudge_controller_->OverrideClockForTesting(&test_clock_); |
| test_clock_.Advance(base::Seconds(360)); |
| } |
| |
| void TearDown() override { |
| nudge_controller_->ClearClockOverrideForTesting(); |
| AshTestBase::TearDown(); |
| } |
| |
| void ShowNudgeForType(ClipboardNudgeType nudge_type) { |
| switch (nudge_type) { |
| case ClipboardNudgeType::kOnboardingNudge: |
| case ClipboardNudgeType::kZeroStateNudge: |
| case ClipboardNudgeType::kDuplicateCopyNudge: |
| nudge_controller_->ShowNudge(nudge_type); |
| return; |
| case ClipboardNudgeType::kScreenshotNotificationNudge: |
| nudge_controller_->MarkScreenshotNotificationShown(); |
| return; |
| } |
| } |
| |
| // Returns an item with arbitrary data for simulating clipboard writes. |
| ClipboardHistoryItem CreateItem() const { |
| ClipboardHistoryItemBuilder builder; |
| builder.SetText("text"); |
| return builder.Build(); |
| } |
| |
| bool IsClipboardNudgeShown() { |
| return IsSystemNudgeMigrationEnabled() |
| ? AnchoredNudgeManager::Get()->IsNudgeShown(kClipboardNudgeId) |
| : nudge_controller_->GetSystemNudgeForTesting() != nullptr; |
| } |
| |
| bool IsSystemNudgeMigrationEnabled() const { |
| return enable_system_nudge_migration_; |
| } |
| |
| base::HistogramTester& histograms() { return histograms_; } |
| |
| // Owned by ClipboardHistoryController. |
| raw_ptr<ClipboardNudgeController, DanglingUntriaged> nudge_controller_; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::SimpleTestClock test_clock_; |
| |
| // Histogram value verifier. |
| base::HistogramTester histograms_; |
| |
| // Parameterized feature flag. |
| const bool enable_system_nudge_migration_; |
| }; |
| |
| // A parameterized test base to verify basic clipboard nudge behavior. |
| class ClipboardNudgeControllerTest |
| : public ClipboardNudgeControllerTestBase, |
| public testing::WithParamInterface< |
| /*enable_system_nudge_migration=*/bool> { |
| public: |
| ClipboardNudgeControllerTest() |
| : ClipboardNudgeControllerTestBase( |
| /*enable_system_nudge_migration=*/GetParam()) {} |
| |
| NudgeCatalogName GetClipboardNudgeCatalogName() { |
| return IsSystemNudgeMigrationEnabled() |
| ? Shell::Get() |
| ->anchored_nudge_manager() |
| ->GetNudgeCatalogNameForTest(kClipboardNudgeId) |
| : nudge_controller_->GetSystemNudgeForTesting()->catalog_name(); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| ClipboardNudgeControllerTest, |
| /*enable_system_nudge_migration=*/testing::Bool()); |
| |
| // Checks that clipboard state advances after the nudge controller is fed the |
| // right event type. |
| TEST_P(ClipboardNudgeControllerTest, ShouldShowNudgeAfterCorrectSequence) { |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kInit, |
| nudge_controller_->onboarding_state_for_testing()); |
| // Checks that the first copy advances state as expected. |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstCopy, |
| nudge_controller_->onboarding_state_for_testing()); |
| |
| // Checks that the first paste advances state as expected. |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| |
| // Checks that the second copy advances state as expected. |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kSecondCopy, |
| nudge_controller_->onboarding_state_for_testing()); |
| |
| // Check that clipboard nudge has not yet been created. |
| EXPECT_FALSE(IsClipboardNudgeShown()); |
| |
| // Checks that the second paste resets state as expected. |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kInit, |
| nudge_controller_->onboarding_state_for_testing()); |
| |
| // Check that clipboard nudge has been created. |
| EXPECT_TRUE(IsClipboardNudgeShown()); |
| } |
| |
| // Checks that the clipboard state does not advace if too much time passes |
| // during the copy paste sequence. |
| TEST_P(ClipboardNudgeControllerTest, NudgeTimeOut) { |
| // Perform copy -> paste -> copy sequence. |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| nudge_controller_->OnClipboardDataRead(); |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| |
| // Advance time to cause the nudge timer to time out. |
| clock()->Advance(kMaxTimeBetweenPaste); |
| nudge_controller_->OnClipboardDataRead(); |
| |
| // Paste event should reset clipboard state to `kFirstPaste`. |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| } |
| |
| // Checks that multiple pastes refreshes the |kMaxTimeBetweenPaste| timer that |
| // determines whether too much time has passed to show the nudge. |
| TEST_P(ClipboardNudgeControllerTest, NudgeDoesNotTimeOutWithSparsePastes) { |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| |
| // Perform 5 pastes over 2.5*|kMaxTimeBetweenPaste|. |
| for (int paste_cycle = 0; paste_cycle < 5; paste_cycle++) { |
| SCOPED_TRACE("paste cycle " + base::NumberToString(paste_cycle)); |
| clock()->Advance(kMaxTimeBetweenPaste / 2); |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| } |
| |
| // Check that clipboard nudge has not yet been created. |
| EXPECT_FALSE(IsClipboardNudgeShown()); |
| |
| // Check that HandleClipboardChanged() will advance nudge_controller's |
| // ClipboardState. |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kSecondCopy, |
| nudge_controller_->onboarding_state_for_testing()); |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kInit, |
| nudge_controller_->onboarding_state_for_testing()); |
| |
| // Check that clipboard nudge has been created. |
| EXPECT_TRUE(IsClipboardNudgeShown()); |
| } |
| |
| // Checks that consecutive copy events does not advance the clipboard state. |
| TEST_P(ClipboardNudgeControllerTest, RepeatedCopyDoesNotAdvanceState) { |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstCopy, |
| nudge_controller_->onboarding_state_for_testing()); |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstCopy, |
| nudge_controller_->onboarding_state_for_testing()); |
| } |
| |
| // Checks that consecutive paste events does not advance the clipboard state. |
| TEST_P(ClipboardNudgeControllerTest, RepeatedPasteDoesNotAdvanceState) { |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstCopy, |
| nudge_controller_->onboarding_state_for_testing()); |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| } |
| |
| // Verifies that administrative events does not advance clipboard state. |
| TEST_P(ClipboardNudgeControllerTest, AdminWriteDoesNotAdvanceState) { |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| nudge_controller_->OnClipboardDataRead(); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| |
| auto data = std::make_unique<ui::ClipboardData>(); |
| data->set_text("test"); |
| // Write the data to the clipboard, clipboard state should not advance. |
| ui::ClipboardNonBacked::GetForCurrentThread()->WriteClipboardData( |
| std::move(data)); |
| EXPECT_EQ(ClipboardNudgeController::OnboardingState::kFirstPaste, |
| nudge_controller_->onboarding_state_for_testing()); |
| } |
| |
| // Verifies that controller cleans up and closes an old nudge before displaying |
| // another one. |
| TEST_P(ClipboardNudgeControllerTest, ShowZeroStateNudgeAfterOngoingNudge) { |
| ShowNudgeForType(ClipboardNudgeType::kOnboardingNudge); |
| ASSERT_TRUE(IsClipboardNudgeShown()); |
| EXPECT_EQ(GetClipboardNudgeCatalogName(), |
| NudgeCatalogName::kClipboardHistoryOnboarding); |
| |
| ShowNudgeForType(ClipboardNudgeType::kZeroStateNudge); |
| ASSERT_TRUE(IsClipboardNudgeShown()); |
| EXPECT_EQ(GetClipboardNudgeCatalogName(), |
| NudgeCatalogName::kClipboardHistoryZeroState); |
| } |
| |
| // Verifies that the nudge cleans up properly during shutdown while it is |
| // animating to hide. |
| TEST_P(ClipboardNudgeControllerTest, NudgeClosingDuringShutdown) { |
| // Nudge shutdown cleanup is tested in anchored_nudge_manager_impl_unittest.cc |
| // in the test `NudgeCloses_OnShutdown`. |
| if (IsSystemNudgeMigrationEnabled()) { |
| GTEST_SKIP(); |
| } |
| |
| ShowNudgeForType(ClipboardNudgeType::kOnboardingNudge); |
| |
| ClipboardNudge* nudge = static_cast<ClipboardNudge*>( |
| nudge_controller_->GetSystemNudgeForTesting()); |
| views::Widget* nudge_widget = nudge->widget(); |
| EXPECT_FALSE(nudge_widget->IsClosed()); |
| EXPECT_EQ(ClipboardNudgeType::kOnboardingNudge, nudge->nudge_type()); |
| |
| // Slow down the duration of the nudge |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::SLOW_DURATION); |
| |
| nudge_controller_->FireHideNudgeTimerForTesting(); |
| ASSERT_TRUE(nudge_widget->GetLayer()->GetAnimator()->is_animating()); |
| } |
| |
| // Asserts that all nudge metric related histograms start at 0. |
| TEST_P(ClipboardNudgeControllerTest, NudgeMetrics_StartAtZero) { |
| histograms().ExpectTotalCount(kClipboardHistoryOnboardingNudgeOpenTime, 0); |
| histograms().ExpectTotalCount(kClipboardHistoryOnboardingNudgePasteTime, 0); |
| histograms().ExpectTotalCount(kClipboardHistoryZeroStateNudgeOpenTime, 0); |
| histograms().ExpectTotalCount(kClipboardHistoryZeroStateNudgePasteTime, 0); |
| histograms().ExpectTotalCount(kClipboardHistoryScreenshotNotificationOpenTime, |
| 0); |
| histograms().ExpectTotalCount( |
| kClipboardHistoryScreenshotNotificationPasteTime, 0); |
| } |
| |
| // Tests that nudge `TimeToAction` metric is logged after the nudge has been |
| // shown and clipboard history is opened through the accelerator. |
| TEST_P(ClipboardNudgeControllerTest, TimeToActionMetric) { |
| constexpr char kNudgeShownCount[] = "Ash.NotifierFramework.Nudge.ShownCount"; |
| constexpr char kNudgeTimeToActionWithin1m[] = |
| "Ash.NotifierFramework.Nudge.TimeToAction.Within1m"; |
| constexpr char kNudgeTimeToActionWithin1h[] = |
| "Ash.NotifierFramework.Nudge.TimeToAction.Within1h"; |
| |
| constexpr NudgeCatalogName kOnboardingCatalogName = |
| NudgeCatalogName::kClipboardHistoryOnboarding; |
| constexpr NudgeCatalogName kZeroStateCatalogName = |
| NudgeCatalogName::kClipboardHistoryZeroState; |
| |
| ShowNudgeForType(ClipboardNudgeType::kOnboardingNudge); |
| task_environment()->FastForwardBy(base::Hours(1)); |
| ShowNudgeForType(ClipboardNudgeType::kZeroStateNudge); |
| |
| // Open clipboard history through the accelerator after one hour of having |
| // shown the onboarding nudge, and right after showing the zero state nudge. |
| nudge_controller_->OnClipboardHistoryMenuShown( |
| /*show_source=*/crosapi::mojom::ClipboardHistoryControllerShowSource:: |
| kAccelerator); |
| |
| histograms().ExpectTotalCount(kNudgeShownCount, 2); |
| histograms().ExpectBucketCount(kNudgeTimeToActionWithin1m, |
| kZeroStateCatalogName, 1); |
| histograms().ExpectBucketCount(kNudgeTimeToActionWithin1h, |
| kOnboardingCatalogName, 1); |
| } |
| |
| // A parameterized test base to verify metric recording for each type of nudge. |
| class ClipboardNudgeMetricTest |
| : public ClipboardNudgeControllerTestBase, |
| public testing::WithParamInterface< |
| std::tuple<ClipboardNudgeType, |
| /*enable_system_nudge_migration=*/bool>> { |
| public: |
| ClipboardNudgeMetricTest() |
| : ClipboardNudgeControllerTestBase( |
| /*enable_system_nudge_migration=*/std::get<1>(GetParam())) { |
| if (GetNudgeType() == ClipboardNudgeType::kDuplicateCopyNudge) { |
| scoped_feature_list_.InitWithFeatures( |
| {chromeos::features::kClipboardHistoryRefresh, |
| chromeos::features::kJelly}, |
| {}); |
| } |
| } |
| |
| ClipboardNudgeType GetNudgeType() const { return std::get<0>(GetParam()); } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| ClipboardNudgeMetricTest, |
| testing::Combine( |
| /*nudge_type=*/testing::ValuesIn(kAllClipboardNudgeTypes), |
| /*enable_system_nudge_migration=*/testing::Bool())); |
| |
| // Tests that the metrics are recorded as expected when opening the standalone |
| // clipboard history menu after a nudge shown. |
| TEST_P(ClipboardNudgeMetricTest, ShowMenuAfterNudgeShown) { |
| const ClipboardNudgeType type_being_tested = GetNudgeType(); |
| ShowNudgeForType(type_being_tested); |
| |
| constexpr int kFastForwardSeconds = 2; |
| clock()->Advance(base::Seconds(kFastForwardSeconds)); |
| nudge_controller_->OnClipboardHistoryMenuShown(/*show_source=*/kAccelerator); |
| |
| // Only the metric that records the time delta between the nudge shown and |
| // the menu opening for `type_being_tested` should be recorded. |
| for (const auto nudge_type : kAllClipboardNudgeTypes) { |
| histograms().ExpectBucketCount(GetMenuOpenTimeDeltaHistogram(nudge_type), |
| kFastForwardSeconds, |
| nudge_type == type_being_tested ? 1 : 0); |
| histograms().ExpectTotalCount( |
| GetClipboardHistoryPasteTimeDeltaHistogram(nudge_type), 0); |
| } |
| } |
| |
| // Tests that the metrics are recorded as expected when pasting the clipboard |
| // history data after a nudge shown. |
| TEST_P(ClipboardNudgeMetricTest, PasteAfterNudgeShown) { |
| const ClipboardNudgeType type_being_tested = GetNudgeType(); |
| ShowNudgeForType(type_being_tested); |
| |
| constexpr int kFastForwardSeconds = 2; |
| clock()->Advance(base::Seconds(kFastForwardSeconds)); |
| nudge_controller_->OnClipboardHistoryPasted(); |
| |
| // Only the metric that records the time delta between the nudge shown and the |
| // clipboard data paste for `type_being_tested` should be recorded. |
| for (const auto nudge_type : kAllClipboardNudgeTypes) { |
| histograms().ExpectTotalCount(GetMenuOpenTimeDeltaHistogram(nudge_type), 0); |
| histograms().ExpectBucketCount( |
| GetClipboardHistoryPasteTimeDeltaHistogram(nudge_type), |
| kFastForwardSeconds, nudge_type == type_being_tested ? 1 : 0); |
| } |
| } |
| |
| // Tests that showing a nudge once allows at most one histogram entry to be |
| // recorded. |
| TEST_P(ClipboardNudgeMetricTest, LogOnceForSingleNudgeShown) { |
| const ClipboardNudgeType type_being_tested = GetNudgeType(); |
| ShowNudgeForType(type_being_tested); |
| |
| constexpr int kFastForwardSeconds = 2; |
| clock()->Advance(base::Seconds(kFastForwardSeconds)); |
| nudge_controller_->OnClipboardHistoryMenuShown(/*show_source=*/kAccelerator); |
| nudge_controller_->OnClipboardHistoryPasted(); |
| |
| for (const auto nudge_type : kAllClipboardNudgeTypes) { |
| histograms().ExpectBucketCount(GetMenuOpenTimeDeltaHistogram(nudge_type), |
| kFastForwardSeconds, |
| nudge_type == type_being_tested ? 1 : 0); |
| histograms().ExpectBucketCount( |
| GetClipboardHistoryPasteTimeDeltaHistogram(nudge_type), |
| kFastForwardSeconds, nudge_type == type_being_tested ? 1 : 0); |
| } |
| |
| nudge_controller_->OnClipboardHistoryMenuShown(/*show_source=*/kAccelerator); |
| nudge_controller_->OnClipboardHistoryPasted(); |
| |
| // The metrics should not be recorded for the second menu shown or clipboard |
| // data paste. |
| for (const auto nudge_type : kAllClipboardNudgeTypes) { |
| histograms().ExpectBucketCount(GetMenuOpenTimeDeltaHistogram(nudge_type), |
| kFastForwardSeconds, |
| nudge_type == type_being_tested ? 1 : 0); |
| histograms().ExpectBucketCount( |
| GetClipboardHistoryPasteTimeDeltaHistogram(nudge_type), |
| kFastForwardSeconds, nudge_type == type_being_tested ? 1 : 0); |
| } |
| } |
| |
| // Tests that new histogram entries can be recorded each time a nudge is shown. |
| TEST_P(ClipboardNudgeMetricTest, LogTwiceAfterShowingTwice) { |
| const ClipboardNudgeType nudge_type = GetNudgeType(); |
| ShowNudgeForType(nudge_type); |
| |
| constexpr int kFirstFastForwardSeconds = 2; |
| clock()->Advance(base::Seconds(kFirstFastForwardSeconds)); |
| nudge_controller_->OnClipboardHistoryMenuShown(/*show_source=*/kAccelerator); |
| nudge_controller_->OnClipboardHistoryPasted(); |
| |
| ShowNudgeForType(nudge_type); |
| |
| // Perform menu shown and data paste but with a different fast forward time |
| // delta. |
| constexpr int kSecondFastForwardSeconds = 5; |
| clock()->Advance(base::Seconds(kSecondFastForwardSeconds)); |
| nudge_controller_->OnClipboardHistoryMenuShown(/*show_source=*/kAccelerator); |
| nudge_controller_->OnClipboardHistoryPasted(); |
| |
| const char* menu_shown_time_delta_histogram = |
| GetMenuOpenTimeDeltaHistogram(nudge_type); |
| histograms().ExpectTotalCount(menu_shown_time_delta_histogram, 2); |
| histograms().ExpectBucketCount(menu_shown_time_delta_histogram, |
| kFirstFastForwardSeconds, 1); |
| histograms().ExpectBucketCount(menu_shown_time_delta_histogram, |
| kSecondFastForwardSeconds, 1); |
| |
| const char* paste_time_delta_histogram = |
| GetClipboardHistoryPasteTimeDeltaHistogram(nudge_type); |
| histograms().ExpectTotalCount(paste_time_delta_histogram, 2); |
| histograms().ExpectBucketCount(paste_time_delta_histogram, |
| kFirstFastForwardSeconds, 1); |
| histograms().ExpectBucketCount(paste_time_delta_histogram, |
| kSecondFastForwardSeconds, 1); |
| } |
| |
| // Tests that showing the standalone clipboard history menu from a source other |
| // than the accelerator does not log any metrics, because the nudge only |
| // mentions the accelerator as an option for opening the menu. |
| TEST_P(ClipboardNudgeMetricTest, NotLogForNonAcceleratorMenuShown) { |
| using crosapi::mojom::ClipboardHistoryControllerShowSource; |
| |
| const ClipboardNudgeType nudge_type = GetNudgeType(); |
| ShowNudgeForType(nudge_type); |
| |
| for (size_t i = |
| static_cast<size_t>(ClipboardHistoryControllerShowSource::kMinValue); |
| i < static_cast<size_t>(ClipboardHistoryControllerShowSource::kMaxValue); |
| ++i) { |
| auto show_source = static_cast<ClipboardHistoryControllerShowSource>(i); |
| if (show_source != kAccelerator) { |
| nudge_controller_->OnClipboardHistoryMenuShown(show_source); |
| } |
| } |
| |
| histograms().ExpectTotalCount(GetMenuOpenTimeDeltaHistogram(nudge_type), 0); |
| histograms().ExpectTotalCount( |
| GetClipboardHistoryPasteTimeDeltaHistogram(nudge_type), 0); |
| } |
| |
| class ClipboardHistoryRefreshNudgeTest |
| : public ClipboardNudgeControllerTestBase, |
| public testing::WithParamInterface< |
| std::tuple</*enable_refresh=*/bool, |
| /*enable_system_nudge_migration=*/bool>> { |
| public: |
| // Time deltas that are less/more than the minimum time interval required to |
| // show a capped nudge. |
| static constexpr base::TimeDelta kLessThanMinInterval = |
| kCappedNudgeMinInterval - base::Hours(1); |
| static constexpr base::TimeDelta kMoreThanMinInterval = |
| kCappedNudgeMinInterval + base::Hours(1); |
| |
| ClipboardHistoryRefreshNudgeTest() |
| : ClipboardNudgeControllerTestBase( |
| /*enable_system_nudge_migration=*/std::get<1>(GetParam())) { |
| std::vector<base::test::FeatureRef> refresh_features = { |
| chromeos::features::kClipboardHistoryRefresh, |
| chromeos::features::kJelly}; |
| |
| if (IsClipboardHistoryRefreshEnabled()) { |
| scoped_feature_list_.InitWithFeatures(refresh_features, {}); |
| } else { |
| scoped_feature_list_.InitWithFeatures({}, refresh_features); |
| } |
| } |
| |
| void HideClipboardNudge() { |
| if (IsSystemNudgeMigrationEnabled()) { |
| AnchoredNudgeManager::Get()->Cancel(kClipboardNudgeId); |
| return; |
| } |
| |
| nudge_controller_->HideNudge(); |
| } |
| |
| bool IsClipboardHistoryRefreshEnabled() const { |
| return std::get<0>(GetParam()); |
| } |
| |
| void WaitForClipboardWriteConfirmed() { |
| EXPECT_TRUE(operation_confirmed_future_.Take()); |
| } |
| |
| private: |
| // ClipboardNudgeControllerTestBase: |
| void SetUp() override { |
| ClipboardNudgeControllerTestBase::SetUp(); |
| Shell::Get() |
| ->clipboard_history_controller() |
| ->set_confirmed_operation_callback_for_test( |
| operation_confirmed_future_.GetRepeatingCallback()); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::test::TestFuture<bool> operation_confirmed_future_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| ClipboardHistoryRefreshNudgeTest, |
| testing::Combine( |
| /*enable_refresh=*/testing::Bool(), |
| /*enable_system_nudge_migration=*/testing::Bool())); |
| |
| // Verifies that the duplicate copy nudge is able to show after the onboarding |
| // nudge hits the shown count limit. |
| TEST_P(ClipboardHistoryRefreshNudgeTest, |
| DuplicateCopyAfterOnboardingNudgesHitLimit) { |
| // Show the onboarding nudge three times. |
| base::HistogramTester histogram_tester; |
| ShowNudgeForType(kOnboardingNudge); |
| clock()->Advance(kMoreThanMinInterval); |
| |
| ShowNudgeForType(kOnboardingNudge); |
| clock()->Advance(kMoreThanMinInterval); |
| |
| ShowNudgeForType(kOnboardingNudge); |
| clock()->Advance(kMoreThanMinInterval); |
| HideClipboardNudge(); |
| histogram_tester.ExpectUniqueSample(kClipboardHistoryOnboardingNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/3); |
| |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Write duplicate text to clipboard history. |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Check the existence of the system nudge. |
| EXPECT_EQ(IsClipboardNudgeShown(), IsClipboardHistoryRefreshEnabled()); |
| |
| // Check the show count of the clipboard history duplicate copy nudge. |
| histogram_tester.ExpectUniqueSample( |
| kClipboardHistoryDuplicateCopyNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/IsClipboardHistoryRefreshEnabled() ? 1 : 0); |
| } |
| |
| // Verifies the show count limit of the duplicate copy nudge. |
| TEST_P(ClipboardHistoryRefreshNudgeTest, DuplicateCopyShowCountLimit) { |
| base::HistogramTester histogram_tester; |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Write duplicate text to clipboard history. |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Write duplicate text to clipboard history for the second time. Advance by |
| // enough time that a capped nudge is able to show. |
| clock()->Advance(kMoreThanMinInterval); |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Write duplicate text to clipboard history for the third time. |
| clock()->Advance(kMoreThanMinInterval); |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Check the show count of the clipboard history duplicate copy nudge. |
| histogram_tester.ExpectUniqueSample( |
| kClipboardHistoryDuplicateCopyNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/IsClipboardHistoryRefreshEnabled() ? 3 : 0); |
| |
| // Write duplicate text to clipboard history for the forth time. |
| HideClipboardNudge(); |
| clock()->Advance(kMoreThanMinInterval); |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Check that the duplicate copy nudge does NOT show if the clipboard history |
| // refresh feature is enabled. Because the show count has hit the limit. |
| EXPECT_FALSE(IsClipboardNudgeShown()); |
| histogram_tester.ExpectUniqueSample( |
| kClipboardHistoryDuplicateCopyNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/IsClipboardHistoryRefreshEnabled() ? 3 : 0); |
| } |
| |
| // Ensure that the duplicate copy nudge is not shown too soon after the previous |
| // showing. |
| TEST_P(ClipboardHistoryRefreshNudgeTest, DuplicateCopyTimeIntervalThreshold) { |
| base::HistogramTester histogram_tester; |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Write duplicate text to clipboard history. |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| histogram_tester.ExpectUniqueSample( |
| kClipboardHistoryDuplicateCopyNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/IsClipboardHistoryRefreshEnabled() ? 1 : 0); |
| HideClipboardNudge(); |
| |
| // Advance the time with an interval less than the threshold. |
| clock()->Advance(kLessThanMinInterval); |
| |
| // Write duplicate text to clipboard history again. |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Check that the duplicate copy nudge does NOT show. |
| EXPECT_FALSE(IsClipboardNudgeShown()); |
| |
| // Check the show count of the clipboard history duplicate copy nudge. |
| histogram_tester.ExpectUniqueSample( |
| kClipboardHistoryDuplicateCopyNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/IsClipboardHistoryRefreshEnabled() ? 1 : 0); |
| } |
| |
| // Checks that showing onboarding nudges after duplicate copy works as expected. |
| TEST_P(ClipboardHistoryRefreshNudgeTest, |
| ShowOnboardingNudgeAfterDuplicateCopy) { |
| base::HistogramTester histogram_tester; |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| |
| // Write duplicate text to clipboard history. |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(u"text"); |
| WaitForClipboardWriteConfirmed(); |
| histogram_tester.ExpectUniqueSample( |
| kClipboardHistoryDuplicateCopyNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/IsClipboardHistoryRefreshEnabled() ? 1 : 0); |
| histogram_tester.ExpectUniqueSample(kClipboardHistoryOnboardingNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/0); |
| HideClipboardNudge(); |
| |
| // Advance the time with an interval less than the threshold. |
| clock()->Advance(kLessThanMinInterval); |
| |
| // Trigger the onboarding nudge by subsequent copies and pastes. |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| nudge_controller_->OnClipboardDataRead(); |
| nudge_controller_->OnClipboardHistoryItemAdded(CreateItem()); |
| nudge_controller_->OnClipboardDataRead(); |
| |
| // The onboarding nudge should NOT show if the duplicate copy nudge showed |
| // before due to the time interval threshold. |
| histogram_tester.ExpectUniqueSample( |
| kClipboardHistoryOnboardingNudgeShowCount, |
| /*sample=*/true, |
| /*expected_bucket_count=*/IsClipboardHistoryRefreshEnabled() ? 0 : 1); |
| } |
| |
| } // namespace ash |