blob: a7d32b110d4c10d85fe46bce2681cb7d3f1d47b1 [file] [log] [blame]
// 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