#include "ash/capture_mode/capture_mode_education_controller.h"
#include "ash/accelerators/accelerator_tracker.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/style/keyboard_shortcut_view.h"
#include "ash/system/toast/anchored_nudge.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/test/ash_test_base.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/time/time.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
namespace ash {
namespace {
constexpr char kCaptureModeNudgeId[] = "kCaptureModeNudge";
constexpr char kNudgeTimeToActionWithin1m[] =
"Ash.NotifierFramework.Nudge.TimeToAction.Within1m";
constexpr char kNudgeTimeToActionWithin1h[] =
"Ash.NotifierFramework.Nudge.TimeToAction.Within1h";
constexpr char kNudgeTimeToActionWithinSession[] =
"Ash.NotifierFramework.Nudge.TimeToAction.WithinSession";
constexpr float kKeyboardImageWidth = 448;
constexpr int kNudgeWorkAreaSpacing = 8;
PrefService* GetPrefService() {
return Shell::Get()->session_controller()->GetActivePrefService();
}
void CancelNudge(const std::string& id) {
Shell::Get()->anchored_nudge_manager()->Cancel(id);
}
}
class CaptureModeEducationControllerTest : public AshTestBase {
public:
CaptureModeEducationControllerTest(const std::string& arm_name = "")
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kCaptureModeEducation,
{{"CaptureModeEducationParam", arm_name}}}},
{});
}
CaptureModeEducationControllerTest(
const CaptureModeEducationControllerTest&) = delete;
CaptureModeEducationControllerTest& operator=(
const CaptureModeEducationControllerTest&) = delete;
~CaptureModeEducationControllerTest() override = default;
static void SetOverrideClock(base::Clock* test_clock) {
CaptureModeEducationController::SetOverrideClockForTesting(test_clock);
}
void ActivateNudgeAndCheckVisibility(ui::KeyboardCode key_code = ui::VKEY_S,
int flags = ui::EF_COMMAND_DOWN |
ui::EF_SHIFT_DOWN) {
PressAndReleaseKey(key_code, flags);
const AnchoredNudge* nudge =
Shell::Get()->anchored_nudge_manager()->GetNudgeIfShown(
kCaptureModeNudgeId);
ASSERT_TRUE(nudge);
EXPECT_TRUE(nudge->GetVisible());
}
void StartSessionAndCheckEducationClosed() {
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
auto* capture_mode_controller = CaptureModeController::Get();
ASSERT_TRUE(capture_mode_controller->IsActive());
const AnchoredNudge* nudge =
Shell::Get()->anchored_nudge_manager()->GetNudgeIfShown(
kCaptureModeNudgeId);
EXPECT_FALSE(nudge);
EXPECT_FALSE(capture_mode_controller->education_controller()
->tutorial_widget_for_test());
capture_mode_controller->Stop();
ASSERT_FALSE(capture_mode_controller->IsActive());
}
void SkipNudgePrefs() {
CaptureModeController::Get()->education_controller()->skip_prefs_for_test_ =
true;
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(CaptureModeEducationControllerTest, Exists) {
ASSERT_EQ(features::kCaptureModeEducationParam.Get(),
features::CaptureModeEducationParam::kShortcutNudge);
ASSERT_TRUE(CaptureModeController::Get()->education_controller());
}
TEST_F(CaptureModeEducationControllerTest, NudgeAppearsOnAcceleratorPressed) {
base::SimpleTestClock test_clock;
CaptureModeEducationControllerTest::SetOverrideClock(&test_clock);
SkipNudgePrefs();
for (auto [tracker_data, metadata] : kAcceleratorTrackerList) {
if (metadata.type != TrackerType::kCaptureMode) {
continue;
}
ActivateNudgeAndCheckVisibility(tracker_data.key_code, tracker_data.flags);
CancelNudge(kCaptureModeNudgeId);
}
}
class CaptureModeEducationShortcutNudgeTest
: public CaptureModeEducationControllerTest {
public:
CaptureModeEducationShortcutNudgeTest()
: CaptureModeEducationControllerTest("ShortcutNudge") {}
CaptureModeEducationShortcutNudgeTest(
const CaptureModeEducationShortcutNudgeTest&) = delete;
CaptureModeEducationShortcutNudgeTest& operator=(
const CaptureModeEducationShortcutNudgeTest&) = delete;
~CaptureModeEducationShortcutNudgeTest() override = default;
};
TEST_F(CaptureModeEducationShortcutNudgeTest, KeyboardShortcutVisible) {
base::SimpleTestClock test_clock;
CaptureModeEducationControllerTest::SetOverrideClock(&test_clock);
test_clock.Advance(base::Hours(25));
PressAndReleaseKey(ui::VKEY_S, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
AnchoredNudge* nudge =
Shell::Get()->anchored_nudge_manager()->GetNudgeIfShown(
kCaptureModeNudgeId);
ASSERT_TRUE(nudge);
EXPECT_TRUE(nudge->GetVisible());
auto* shortcut_view = views::AsViewClass<KeyboardShortcutView>(
nudge->GetContentsView()->GetViewByID(
VIEW_ID_SYSTEM_NUDGE_SHORTCUT_VIEW));
ASSERT_TRUE(shortcut_view);
EXPECT_TRUE(shortcut_view->GetVisible());
StartSessionAndCheckEducationClosed();
CaptureModeEducationControllerTest::SetOverrideClock(nullptr);
}
TEST_F(CaptureModeEducationShortcutNudgeTest, EducationPreferencesShowLimit) {
base::SimpleTestClock test_clock;
CaptureModeEducationControllerTest::SetOverrideClock(&test_clock);
test_clock.Advance(base::Hours(25));
AnchoredNudgeManagerImpl* nudge_manager =
Shell::Get()->anchored_nudge_manager();
for (int i = 0; i < 3; i++) {
ActivateNudgeAndCheckVisibility();
EXPECT_EQ(
GetPrefService()->GetInteger(prefs::kCaptureModeEducationShownCount),
i + 1);
CancelNudge(kCaptureModeNudgeId);
test_clock.Advance(base::Hours(25));
}
PressAndReleaseKey(ui::VKEY_S, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
const AnchoredNudge* nudge =
nudge_manager->GetNudgeIfShown(kCaptureModeNudgeId);
ASSERT_FALSE(nudge);
EXPECT_EQ(
GetPrefService()->GetInteger(prefs::kCaptureModeEducationShownCount), 3);
CaptureModeEducationControllerTest::SetOverrideClock(nullptr);
}
TEST_F(CaptureModeEducationShortcutNudgeTest, EducationPreferencesTimeLimit) {
base::SimpleTestClock test_clock;
CaptureModeEducationControllerTest::SetOverrideClock(&test_clock);
test_clock.Advance(base::Hours(25));
ActivateNudgeAndCheckVisibility();
EXPECT_EQ(
GetPrefService()->GetInteger(prefs::kCaptureModeEducationShownCount), 1);
CancelNudge(kCaptureModeNudgeId);
test_clock.Advance(base::Hours(20));
PressAndReleaseKey(ui::VKEY_S, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
const AnchoredNudge* null_nudge =
Shell::Get()->anchored_nudge_manager()->GetNudgeIfShown(
kCaptureModeNudgeId);
ASSERT_FALSE(null_nudge);
EXPECT_EQ(
GetPrefService()->GetInteger(prefs::kCaptureModeEducationShownCount), 1);
CaptureModeEducationControllerTest::SetOverrideClock(nullptr);
}
TEST_F(CaptureModeEducationShortcutNudgeTest, ShortcutNudgeMetrics) {
base::HistogramTester histogram_tester;
SkipNudgePrefs();
Shell::Get()->anchored_nudge_manager()->ResetNudgeRegistryForTesting();
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1h,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithinSession,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutTutorial, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationQuickSettingsNudge, 0);
ActivateNudgeAndCheckVisibility();
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
auto* capture_mode_controller = CaptureModeController::Get();
ASSERT_TRUE(capture_mode_controller->IsActive());
capture_mode_controller->Stop();
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 1);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1h,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithinSession,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutTutorial, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationQuickSettingsNudge, 0);
ActivateNudgeAndCheckVisibility();
task_environment()->FastForwardBy(base::Minutes(30));
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
ASSERT_TRUE(capture_mode_controller->IsActive());
capture_mode_controller->Stop();
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 1);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1h,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 1);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithinSession,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
ActivateNudgeAndCheckVisibility();
task_environment()->FastForwardBy(base::Hours(2));
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
ASSERT_TRUE(capture_mode_controller->IsActive());
capture_mode_controller->Stop();
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 1);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1h,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 1);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithinSession,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 1);
}
class CaptureModeEducationShortcutTutorialTest
: public CaptureModeEducationControllerTest {
public:
CaptureModeEducationShortcutTutorialTest()
: CaptureModeEducationControllerTest("ShortcutTutorial") {}
CaptureModeEducationShortcutTutorialTest(
const CaptureModeEducationShortcutTutorialTest&) = delete;
CaptureModeEducationShortcutTutorialTest& operator=(
const CaptureModeEducationShortcutTutorialTest&) = delete;
~CaptureModeEducationShortcutTutorialTest() override = default;
};
TEST_F(CaptureModeEducationShortcutTutorialTest, DialogOpensAndCloses) {
base::SimpleTestClock test_clock;
CaptureModeEducationControllerTest::SetOverrideClock(&test_clock);
test_clock.Advance(base::Hours(25));
PressAndReleaseKey(ui::VKEY_S, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
AnchoredNudgeManagerImpl* nudge_manager =
Shell::Get()->anchored_nudge_manager();
LeftClickOn(nudge_manager->GetNudgePrimaryButtonForTest(kCaptureModeNudgeId));
EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kCaptureModeNudgeId));
auto* tutorial_widget = CaptureModeController::Get()
->education_controller()
->tutorial_widget_for_test();
ASSERT_TRUE(tutorial_widget);
auto* image_view = tutorial_widget->GetContentsView()->GetViewByID(
VIEW_ID_SCREEN_CAPTURE_EDUCATION_KEYBOARD_IMAGE);
ASSERT_TRUE(image_view);
EXPECT_EQ(kKeyboardImageWidth, image_view->width());
StartSessionAndCheckEducationClosed();
}
TEST_F(CaptureModeEducationShortcutTutorialTest, ShortcutTutorialMetrics) {
base::HistogramTester histogram_tester;
SkipNudgePrefs();
Shell::Get()->anchored_nudge_manager()->ResetNudgeRegistryForTesting();
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutTutorial, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationQuickSettingsNudge, 0);
ActivateNudgeAndCheckVisibility();
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutTutorial, 1);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationQuickSettingsNudge, 0);
}
class CaptureModeEducationQuickSettingsNudgeTest
: public CaptureModeEducationControllerTest {
public:
CaptureModeEducationQuickSettingsNudgeTest()
: CaptureModeEducationControllerTest("QuickSettingsNudge") {}
CaptureModeEducationQuickSettingsNudgeTest(
const CaptureModeEducationQuickSettingsNudgeTest&) = delete;
CaptureModeEducationQuickSettingsNudgeTest& operator=(
const CaptureModeEducationQuickSettingsNudgeTest&) = delete;
~CaptureModeEducationQuickSettingsNudgeTest() override = default;
};
TEST_F(CaptureModeEducationQuickSettingsNudgeTest, NudgeLocation) {
UpdateDisplay("800x600");
base::SimpleTestClock test_clock;
CaptureModeEducationControllerTest::SetOverrideClock(&test_clock);
test_clock.Advance(base::Hours(25));
PressAndReleaseKey(ui::VKEY_S, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
AnchoredNudge* nudge =
Shell::Get()->anchored_nudge_manager()->GetNudgeIfShown(
kCaptureModeNudgeId);
ASSERT_TRUE(nudge);
EXPECT_TRUE(nudge->GetVisible());
ASSERT_TRUE(Shell::GetPrimaryRootWindowController()
->shelf()
->IsHorizontalAlignment());
const auto nudge_corner = nudge->GetBoundsInScreen().bottom_right();
auto work_area_corner =
nudge->GetWidget()->GetWorkAreaBoundsInScreen().bottom_right();
EXPECT_EQ(work_area_corner.x() - kNudgeWorkAreaSpacing, nudge_corner.x());
EXPECT_EQ(work_area_corner.y() - kNudgeWorkAreaSpacing, nudge_corner.y());
StartSessionAndCheckEducationClosed();
CaptureModeEducationControllerTest::SetOverrideClock(nullptr);
}
TEST_F(CaptureModeEducationQuickSettingsNudgeTest, SettingsNudgeMetrics) {
base::HistogramTester histogram_tester;
SkipNudgePrefs();
Shell::Get()->anchored_nudge_manager()->ResetNudgeRegistryForTesting();
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationQuickSettingsNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutTutorial, 0);
ActivateNudgeAndCheckVisibility();
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationQuickSettingsNudge, 1);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutNudge, 0);
histogram_tester.ExpectBucketCount(
kNudgeTimeToActionWithin1m,
NudgeCatalogName::kCaptureModeEducationShortcutTutorial, 0);
}
class CaptureModeEducationControllerBypassLimitsFlagTest
: public CaptureModeEducationControllerTest {
public:
CaptureModeEducationControllerBypassLimitsFlagTest() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kCaptureModeEducation,
{{"CaptureModeEducationParam", "ShortcutNudge"}}},
{features::kCaptureModeEducationBypassLimits, {}}},
{});
}
CaptureModeEducationControllerBypassLimitsFlagTest(
const CaptureModeEducationControllerBypassLimitsFlagTest&) = delete;
CaptureModeEducationControllerBypassLimitsFlagTest& operator=(
const CaptureModeEducationControllerBypassLimitsFlagTest&) = delete;
~CaptureModeEducationControllerBypassLimitsFlagTest() override = default;
protected:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(CaptureModeEducationControllerBypassLimitsFlagTest, NoShowLimits) {
for (int i = 0; i < 4; i++) {
ActivateNudgeAndCheckVisibility();
CancelNudge(kCaptureModeNudgeId);
}
}
}