0

[iOS] Create Reminder Notifications Overflow Menu Badge IPH Feature

This CL introduces a new Feature Engagement Tracker (FET) feature for a
new badge displayed on the "Set a Reminder" action in the overflow menu.
It tracks user interaction with this action and displays the badge to
promote its availability and encourage adoption. Specifically, this CL:

- Adds new events and feature constants related to the "Set a Reminder"
  badge.

- Implements the FET configuration for the new badge IPH, including
  trigger and usage conditions.

- Integrates the FET with the overflow menu mediator to display the new
  badge and track user taps on the "Set a Reminder" action.

- Adds unit tests to verify the correct behavior of the new FET feature.

- Updates FET metrics actions and histograms.

Demo video:
https://drive.google.com/file/d/1vVKgm1-EtwdPfYdeZBRe_rsMYxl1raRg/view?usp=sharing&resourcekey=0-LChnR7dZXG4LzITfxuqAUQ

Change-Id: If9e4b8a92cb44e71b2f46f86c0f3e89c470dc98b
Fixed: 
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6233853
Commit-Queue: Benjamin Williams <bwwilliams@google.com>
Reviewed-by: Robbie Gibson <rkgibson@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1421448}
This commit is contained in:
Benjamin Williams
2025-02-18 09:40:15 -08:00
committed by Chromium LUCI CQ
parent 0a84f818cf
commit b17d7c4a73
11 changed files with 199 additions and 14 deletions
components/feature_engagement/public
ios/chrome/browser
feature_engagement
ui
popup_menu
tools/metrics
actions
histograms
metadata
feature_engagement

@ -148,6 +148,10 @@ const char kLensOverlayEntrypointUsed[] = "lens_overlay_entrypoint_used";
const char kIOSTabReminderScheduled[] = "tab_reminder_scheduled";
const char kIOSReminderNotificationsOverflowMenuBubbleIPHTrigger[] =
"ios_reminder_notifications_overflow_menu_bubble_iph_trigger";
const char kIOSOverflowMenuSetTabReminderTapped[] =
"ios_overflow_menu_set_tab_reminder_tapped";
const char kIOSReminderNotificationsOverflowMenuNewBadgeIPHTrigger[] =
"ios_reminder_notifications_overflow_menu_new_badge_iph_trigger";
#endif // BUILDFLAG(IS_IOS)
#if BUILDFLAG(IS_ANDROID)

@ -287,6 +287,11 @@ extern const char kIOSTabReminderScheduled[];
// The Reminder Notifications Overflow Menu Bubble IPH was triggered.
extern const char kIOSReminderNotificationsOverflowMenuBubbleIPHTrigger[];
// The user tapped the "Set a Reminder" item in the overflow menu.
extern const char kIOSOverflowMenuSetTabReminderTapped[];
// The Reminder Notifications Overflow Menu New Badge IPH was triggered.
extern const char kIOSReminderNotificationsOverflowMenuNewBadgeIPHTrigger[];
#endif // BUILDFLAG(IS_IOS)
// Android.

@ -2205,6 +2205,35 @@ std::optional<FeatureConfig> GetClientSideFeatureConfig(
return config;
}
if (kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature.name ==
feature->name) {
FeatureConfig config;
config.valid = true;
// No availability requirement for this feature.
config.availability = Comparator(ANY, 0);
// No session rate limit for this feature.
config.session_rate = Comparator(ANY, 0);
// Initially, show to users who haven't tapped the "Set a Reminder" overflow
// menu action yet.
config.used = EventConfig(
feature_engagement::events::kIOSOverflowMenuSetTabReminderTapped,
Comparator(EQUAL, 0), feature_engagement::kMaxStoragePeriod,
feature_engagement::kMaxStoragePeriod);
// The New Badge IPH should not be triggered more than 3 times
// in total.
config.trigger = EventConfig(
feature_engagement::events::
kIOSReminderNotificationsOverflowMenuNewBadgeIPHTrigger,
Comparator(LESS_THAN, 3), feature_engagement::kMaxStoragePeriod,
feature_engagement::kMaxStoragePeriod);
// Don't show if the user has already scheduled a tab reminder.
config.event_configs.insert(
EventConfig(feature_engagement::events::kIOSTabReminderScheduled,
Comparator(EQUAL, 0), feature_engagement::kMaxStoragePeriod,
feature_engagement::kMaxStoragePeriod));
return config;
}
if (kIPHiOSReplaceSyncPromosWithSignInPromos.name == feature->name) {
// A config to show a user education bubble from the account row in the
// settings page. Will be shown only the first time user signs-in from

@ -653,6 +653,9 @@ BASE_FEATURE(kIPHiOSDefaultBrowserBannerPromoFeature,
BASE_FEATURE(kIPHiOSReminderNotificationsOverflowMenuBubbleFeature,
"IPH_iOSReminderNotificationsOverflowMenuBubbleFeature",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature,
"IPH_iOSReminderNotificationsOverflowMenuNewBadgeFeature",
base::FEATURE_ENABLED_BY_DEFAULT);
// Non-FET feature.
BASE_FEATURE(kDefaultBrowserEligibilitySlidingWindow,

@ -276,6 +276,8 @@ FEATURE_CONSTANTS_DECLARE_FEATURE(kIPHiOSSharedTabGroupForeground);
FEATURE_CONSTANTS_DECLARE_FEATURE(kIPHiOSDefaultBrowserBannerPromoFeature);
FEATURE_CONSTANTS_DECLARE_FEATURE(
kIPHiOSReminderNotificationsOverflowMenuBubbleFeature);
FEATURE_CONSTANTS_DECLARE_FEATURE(
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature);
// A feature flag to enable and parametrize the sliding window of time for a
// user's eligibility to be shown a default browser promo. This is not an FET

@ -163,6 +163,7 @@ const base::Feature* const kAllFeatures[] = {
&kIPHiOSSharedTabGroupForeground,
&kIPHiOSDefaultBrowserBannerPromoFeature,
&kIPHiOSReminderNotificationsOverflowMenuBubbleFeature,
&kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature,
#endif // BUILDFLAG(IS_IOS)
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)

@ -288,6 +288,9 @@ DEFINE_VARIATION_PARAM(kIPHiOSDefaultBrowserBannerPromoFeature,
"IPH_iOSDefaultBrowserBannerPromoFeature");
DEFINE_VARIATION_PARAM(kIPHiOSReminderNotificationsOverflowMenuBubbleFeature,
"IPH_iOSReminderNotificationsOverflowMenuBubbleFeature");
DEFINE_VARIATION_PARAM(
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature,
"IPH_iOSReminderNotificationsOverflowMenuNewBadgeFeature");
#endif // BUILDFLAG(IS_IOS)
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
@ -665,6 +668,8 @@ inline constexpr flags_ui::FeatureEntry::FeatureVariation
VARIATION_ENTRY(kIPHiOSSharedTabGroupForeground),
VARIATION_ENTRY(kIPHiOSDefaultBrowserBannerPromoFeature),
VARIATION_ENTRY(kIPHiOSReminderNotificationsOverflowMenuBubbleFeature),
VARIATION_ENTRY(
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature),
#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
VARIATION_ENTRY(kIPHBatterySaverModeFeature),

@ -1051,3 +1051,108 @@ TEST_F(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuBubbleFeature));
}
// Verifies the Reminder Notifications Overflow Menu New Badge IPH should
// trigger when preconditions are met.
TEST_F(FeatureEngagementTest,
TestReminderNotificationsOverflowMenuNewBadgeIPHShouldTrigger) {
feature_engagement::test::ScopedIphFeatureList list;
list.InitAndEnableFeatures(
{feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature});
std::unique_ptr<feature_engagement::Tracker> tracker =
feature_engagement::CreateTestTracker();
// Ensures the tracker is initialized.
tracker->AddOnInitializedCallback(BoolArgumentQuitClosure());
run_loop_.Run();
EXPECT_TRUE(tracker->ShouldTriggerHelpUI(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature));
tracker->Dismissed(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature);
}
// Verifies the Reminder Notifications Overflow Menu New Badge IPH should not
// trigger after tapping "Set a Reminder" (used event).
TEST_F(
FeatureEngagementTest,
TestReminderNotificationsOverflowMenuNewBadgeIPHShouldNotTriggerAfterTapped) {
feature_engagement::test::ScopedIphFeatureList list;
list.InitAndEnableFeatures(
{feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature});
std::unique_ptr<feature_engagement::Tracker> tracker =
feature_engagement::CreateTestTracker();
// Ensures the tracker is initialized.
tracker->AddOnInitializedCallback(BoolArgumentQuitClosure());
run_loop_.Run();
// Simulates tapping "Set a Reminder" (used event).
tracker->NotifyEvent(
feature_engagement::events::kIOSOverflowMenuSetTabReminderTapped);
EXPECT_FALSE(tracker->ShouldTriggerHelpUI(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature));
}
// Verifies the Reminder Notifications Overflow Menu New Badge IPH should not
// trigger if a tab reminder was already scheduled (used event - historical
// usage).
TEST_F(
FeatureEngagementTest,
TestReminderNotificationsOverflowMenuNewBadgeIPHShouldNotTriggerAfterReminderScheduled) {
feature_engagement::test::ScopedIphFeatureList list;
list.InitAndEnableFeatures(
{feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature});
std::unique_ptr<feature_engagement::Tracker> tracker =
feature_engagement::CreateTestTracker();
// Ensures the tracker is initialized.
tracker->AddOnInitializedCallback(BoolArgumentQuitClosure());
run_loop_.Run();
// Simulates a reminder already scheduled (used event).
tracker->NotifyEvent(feature_engagement::events::kIOSTabReminderScheduled);
EXPECT_FALSE(tracker->ShouldTriggerHelpUI(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature));
}
// Verifies the Reminder Notifications Overflow Menu New Badge IPH should not
// trigger after reaching the trigger limit (3 times).
TEST_F(
FeatureEngagementTest,
TestReminderNotificationsOverflowMenuNewBadgeIPHShouldNotTriggerAfterLimit) {
feature_engagement::test::ScopedIphFeatureList list;
list.InitAndEnableFeatures(
{feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature});
std::unique_ptr<feature_engagement::Tracker> tracker =
feature_engagement::CreateTestTracker();
// Ensures the tracker is initialized.
tracker->AddOnInitializedCallback(BoolArgumentQuitClosure());
run_loop_.Run();
// Trigger IPH 3 times.
for (int i = 0; i < 3; ++i) {
EXPECT_TRUE(tracker->ShouldTriggerHelpUI(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature));
tracker->Dismissed(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature);
}
// IPH should not trigger again after reaching the limit.
EXPECT_FALSE(tracker->ShouldTriggerHelpUI(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature));
}

@ -826,20 +826,47 @@ OverflowMenuFooter* CreateOverflowMenuManagedFooter(
NSString* hideItemText = l10n_util::GetNSString(
IDS_IOS_REMINDER_NOTIFICATIONS_HIDE_SET_A_REMINDER);
return
[self createOverflowMenuActionWithNameID:
IDS_IOS_REMINDER_NOTIFICATIONS_SET_A_REMINDER
actionType:overflow_menu::ActionType::
SetTabReminder
symbolName:kBellBadgeSymbol
systemSymbol:YES
monochromeSymbol:NO
accessibilityID:kToolsMenuSetTabReminder
hideItemText:hideItemText
handler:^{
// TODO(crbug.com/389912106): Display
// the new 'Set a Reminder' UI.
}];
__weak __typeof(self) weakSelf = self;
OverflowMenuAction* action = [self
createOverflowMenuActionWithNameID:
IDS_IOS_REMINDER_NOTIFICATIONS_SET_A_REMINDER
actionType:overflow_menu::ActionType::
SetTabReminder
symbolName:kBellBadgeSymbol
systemSymbol:YES
monochromeSymbol:NO
accessibilityID:kToolsMenuSetTabReminder
hideItemText:hideItemText
handler:^{
[weakSelf notifySetTabReminderActionTapped];
}];
if (_engagementTracker &&
_engagementTracker->ShouldTriggerHelpUI(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature)) {
action.displayNewLabelIcon = YES;
_engagementTracker->Dismissed(
feature_engagement::
kIPHiOSReminderNotificationsOverflowMenuNewBadgeFeature);
}
return action;
}
// Notifies the FET that the user tapped the "Set a Reminder" action.
- (void)notifySetTabReminderActionTapped {
CHECK(
send_tab_to_self::IsSendTabIOSPushNotificationsEnabledWithTabReminders());
if (_engagementTracker) {
_engagementTracker->NotifyEvent(
feature_engagement::events::kIOSOverflowMenuSetTabReminderTapped);
}
// TODO(crbug.com/389912106): Display the new 'Set a Reminder' UI.
}
- (OverflowMenuAction*)newClearBrowsingDataAction {

@ -47685,6 +47685,8 @@ should be able to be added at any place in this file.
label="For iOSPullToRefreshFeature feature"/>
<suffix name="iOSReminderNotificationsOverflowMenuBubbleFeature"
label="For iOSReminderNotificationsOverflowMenuBubbleFeature feature"/>
<suffix name="iOSReminderNotificationsOverflowMenuNewBadgeFeature"
label="For iOSReminderNotificationsOverflowMenuNewBadgeFeature feature"/>
<suffix name="iOSReplaceSyncPromosWithSignInPromos"
label="For iOSReplaceSyncPromosWithSignInPromos feature"/>
<suffix name="iOSSavedTabGroupClosed"

@ -318,6 +318,8 @@ chromium-metrics-reviews@google.com.
summary="the full screen IPH for pull to refresh"/>
<variant name="IPH_iOSReminderNotificationsOverflowMenuBubbleFeature"
summary="Overflow menu bubble IPH to schedule tab reminders"/>
<variant name="IPH_iOSReminderNotificationsOverflowMenuNewBadgeFeature"
summary="Overflow menu new badge IPH to schedule tab reminders"/>
<variant name="IPH_iOSReplaceSyncPromosWithSignInPromos"
summary="account row in settings page"/>
<variant name="IPH_iOSSavedTabGroupClosed"