Skip to content

Commit

Permalink
[iOS] Change default browser blue dot impression limit to max 15 days
Browse files Browse the repository at this point in the history
Instead of limiting number of times blue dot can be displayed instead
show the blue dot until one of the stop criteria is met for max to
15 days.

In this CL also change the cooldown from FRE to 21 days.

Bug: 338249046
Change-Id: I6239710f2f4b4b688a1caa3fb677235d6bb7ef63
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5685292
Commit-Queue: Gayane Petrosyan <gayane@google.com>
Reviewed-by: Nicolas MacBeth <nicolasmacbeth@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1328368}
  • Loading branch information
Gayane Petrosyan authored and pull[bot] committed Jul 19, 2024
1 parent 032e599 commit 1489924
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 118 deletions.
4 changes: 0 additions & 4 deletions components/feature_engagement/public/event_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,7 @@ const char kPasswordManagerWidgetPromoClosed[] =
// Default browser blue dot promo.
const char kBlueDotPromoOverflowMenuShown[] =
"blue_dot_promo_overflow_menu_shown";
const char kBlueDotPromoOverflowMenuShownNewSession[] =
"blue_dot_promo_overflow_menu_shown_new_session";
const char kBlueDotPromoSettingsShown[] = "blue_dot_promo_settings_shown";
const char kBlueDotPromoSettingsShownNewSession[] =
"blue_dot_promo_settings_shown_new_session";
const char kBlueDotPromoOverflowMenuDismissed[] =
"blue_dot_promo_overflow_menu_dismissed";
const char kBlueDotPromoSettingsDismissed[] =
Expand Down
16 changes: 0 additions & 16 deletions components/feature_engagement/public/event_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,30 +143,14 @@ extern const char kPasswordManagerWidgetPromoUsed[];
// The Password Manager widget promo was closed.
extern const char kPasswordManagerWidgetPromoClosed[];

// The user has taken an action that is a criterion towards becoming eligible to
// be shown the blue dot default browser promo.
extern const char kBlueDotPromoCriterionMet[];

// The user has met all criteria and has become eligible to be shown the blue
// dot default browser promo.
extern const char kBlueDotPromoEligibilityMet[];

// The user has been shown the blue dot default browser promo on the overflow
// carousel.
extern const char kBlueDotPromoOverflowMenuShown[];

// The user has been shown the blue dot default browser promo on the overflow
// carousel, for a new user session. (i.e. after 6 hours from last shown).
extern const char kBlueDotPromoOverflowMenuShownNewSession[];

// The user has been shown the blue dot default browser promo on the settings
// row.
extern const char kBlueDotPromoSettingsShown[];

// The user has been shown the blue dot default browser promo on the settings
// row, after a new user session (i.e. after 6 hours from last shown).
extern const char kBlueDotPromoSettingsShownNewSession[];

// The user has dismissed the blue dot default browser promo on the overflow
// carousel.
extern const char kBlueDotPromoOverflowMenuDismissed[];
Expand Down
20 changes: 12 additions & 8 deletions components/feature_engagement/public/feature_configurations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1887,17 +1887,23 @@ std::optional<FeatureConfig> GetClientSideFeatureConfig(
config->availability = Comparator(ANY, 0);
config->session_rate = Comparator(ANY, 0);
config->session_rate_impact.type = SessionRateImpact::Type::NONE;
config->used = EventConfig("blue_dot_promo_overflow_menu_dismissed",
Comparator(EQUAL, 0), 30, 360);
config->trigger = EventConfig("blue_dot_promo_overflow_menu_shown",
Comparator(ANY, 0), 360, 360);
config->event_configs.insert(
EventConfig("blue_dot_promo_overflow_menu_shown_new_session",
Comparator(LESS_THAN_OR_EQUAL, 2), 360, 360));
// Stop showing blue dot promo if settings was tapped at least 3 times.
config->used = EventConfig("blue_dot_promo_overflow_menu_dismissed",
Comparator(LESS_THAN, 3), 360, 360);
// Stop showing blue dot promo if default browser settings was opened at
// least once.
config->event_configs.insert(EventConfig(
"blue_dot_promo_settings_dismissed", Comparator(EQUAL, 0), 360, 360));
// TODO(crbug.com/338249422): Stop showing blue dot promo if overflow menu
// was customized while blue dot was showing.

// Cooldowns from other default browser promos.
config->event_configs.insert(EventConfig("default_browser_promo_shown",
Comparator(EQUAL, 0), 14, 360));
config->event_configs.insert(EventConfig("default_browser_fre_shown",
Comparator(EQUAL, 0), 14, 360));
Comparator(EQUAL, 0), 21, 360));
config->event_configs.insert(EventConfig(
"default_browser_promos_group_trigger", Comparator(EQUAL, 0), 14, 360));
config->event_configs.insert(
Expand All @@ -1906,8 +1912,6 @@ std::optional<FeatureConfig> GetClientSideFeatureConfig(

// Continue checking deprecated settings badge conditions to not show blue
// dot at all if user would not have qualified for settings badge.
config->event_configs.insert(EventConfig(
"blue_dot_promo_settings_dismissed", Comparator(EQUAL, 0), 30, 360));
config->event_configs.insert(
EventConfig("blue_dot_promo_settings_shown_new_session",
Comparator(LESS_THAN_OR_EQUAL, 2), 360, 360));
Expand Down
6 changes: 6 additions & 0 deletions ios/chrome/browser/default_browser/model/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,14 @@ source_set("unit_tests") {
"//base/test:test_support",
"//components/feature_engagement/public",
"//components/feature_engagement/test:test_support",
"//components/prefs:test_support",
"//components/sync_preferences:test_support",
"//ios/chrome/browser/default_browser/model:utils",
"//ios/chrome/browser/shared/model/browser_state:test_support",
"//ios/chrome/browser/shared/model/prefs:browser_prefs",
"//ios/chrome/browser/shared/model/prefs:pref_names",
"//ios/chrome/browser/shared/public/features",
"//ios/chrome/test:test_support",
"//ios/web/public/test",
"//testing/gtest",
"//ui/base",
Expand Down
21 changes: 17 additions & 4 deletions ios/chrome/browser/default_browser/model/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Tracker;
}
namespace base {
class Time;
class TimeDelta;
}

// Enum for the different types of default browser modal promo. These are stored
Expand Down Expand Up @@ -140,6 +141,12 @@ extern NSString* const kTailoredPromoInteractionCount;
// started.
extern NSString* const kTimestampTriggerCriteriaExperimentStarted;

// Specifies how long blue dot occurrence should last.
extern base::TimeDelta const kBlueDotPromoDuration;

// Specifies how often blue dot should reoccur.
extern base::TimeDelta const kBlueDotPromoReoccurrancePeriod;

// Loads from NSUserDefaults the time of the non-expired events for the
// given promo type.
std::vector<base::Time> LoadTimestampsForPromoType(DefaultPromoType type);
Expand All @@ -163,6 +170,16 @@ void LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoType type);
// Logs to the FET that a default browser promo has been shown.
void LogToFETDefaultBrowserPromoShown(feature_engagement::Tracker* tracker);

// Returns whether blue dot display timestamp has already been set.
bool HasDefaultBrowserBlueDotDisplayTimestamp();

// Resets blue dot display timestamp to its default value when needed.
void ResetDefaultBrowserBlueDotDisplayTimestampIfNeeded();

// Set the current timestamp as blue dot first display timestamp if this was the
// first instance.
void RecordDefaultBrowserBlueDotFirstDisplay();

// Returns true if the default browser blue dot should be shown.
bool ShouldTriggerDefaultBrowserHighlightFeature(
feature_engagement::Tracker* tracker);
Expand Down Expand Up @@ -244,10 +261,6 @@ void LogAutofillUseForCriteriaExperiment();
// Logs that the user has used remote tabs.
void LogRemoteTabsUseForCriteriaExperiment();

// Returns YES if the last timestamp passed as `eventKey` is part of the current
// user session (default 6 hours). If not, it records the timestamp.
bool HasRecentTimestampForKey(NSString* eventKey);

// Returns true if the last URL open is within the specified number of `days`
// which would indicate Chrome is likely still the default browser. Returns
// false otherwise.
Expand Down
56 changes: 45 additions & 11 deletions ios/chrome/browser/default_browser/model/utils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ void StoreCurrentTimestampForKey(NSString* key) {
NSString* const kGenericPromoInteractionCount = @"genericPromoInteractionCount";
NSString* const kTailoredPromoInteractionCount =
@"tailoredPromoInteractionCount";
constexpr base::TimeDelta kBlueDotPromoDuration = base::Days(15);
constexpr base::TimeDelta kBlueDotPromoReoccurrancePeriod = base::Days(360);

// Migration to FET keys.
NSString* const kFRETimestampMigrationDone = @"fre_timestamp_migration_done";
Expand Down Expand Up @@ -448,12 +450,55 @@ void LogToFETDefaultBrowserPromoShown(feature_engagement::Tracker* tracker) {
tracker->NotifyEvent(feature_engagement::events::kDefaultBrowserPromoShown);
}

bool HasDefaultBrowserBlueDotDisplayTimestamp() {
return !GetApplicationContext()
->GetLocalState()
->FindPreference(
prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay)
->IsDefaultValue();
}

void ResetDefaultBrowserBlueDotDisplayTimestampIfNeeded() {
BOOL has_timestamp = HasDefaultBrowserBlueDotDisplayTimestamp();

if (!has_timestamp) {
return;
}

base::Time timestamp = GetApplicationContext()->GetLocalState()->GetTime(
prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay);

// If more than `kBlueDotPromoReoccurrancePeriod` past since previous blue
// dot display, user should again become eligible for blue dot promo.
if (base::Time::Now() - timestamp >= kBlueDotPromoReoccurrancePeriod) {
GetApplicationContext()->GetLocalState()->ClearPref(
prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay);
}
}

void RecordDefaultBrowserBlueDotFirstDisplay() {
if (!HasDefaultBrowserBlueDotDisplayTimestamp()) {
GetApplicationContext()->GetLocalState()->SetTime(
prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay, base::Time::Now());
}
}

bool ShouldTriggerDefaultBrowserHighlightFeature(
feature_engagement::Tracker* tracker) {
if (IsChromeLikelyDefaultBrowser()) {
return false;
}

ResetDefaultBrowserBlueDotDisplayTimestampIfNeeded();

if (HasDefaultBrowserBlueDotDisplayTimestamp()) {
base::Time timestamp = GetApplicationContext()->GetLocalState()->GetTime(
prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay);
if (base::Time::Now() - timestamp >= kBlueDotPromoDuration) {
return false;
}
}

// We ask the appropriate FET feature if it should trigger, i.e. if we
// should show the blue dot promo badge.
if (tracker->ShouldTriggerHelpUI(
Expand Down Expand Up @@ -639,17 +684,6 @@ void LogRemoteTabsUseForCriteriaExperiment() {
StoreCurrentTimestampForKey(kSpecialTabsUseCount);
}

bool HasRecentTimestampForKey(NSString* eventKey) {
const base::TimeDelta max_session_time = base::Hours(6);

if (HasRecordedEventForKeyLessThanDelay(eventKey, max_session_time)) {
return YES;
}

SetObjectIntoStorageForKey(eventKey, [NSDate date]);
return NO;
}

bool IsChromeLikelyDefaultBrowserXDays(int days) {
return HasRecordedEventForKeyLessThanDelay(kLastHTTPURLOpenTime,
base::Days(days));
Expand Down
98 changes: 68 additions & 30 deletions ios/chrome/browser/default_browser/model/utils_unittest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@
#import "base/test/scoped_feature_list.h"
#import "base/time/time.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/prefs/testing_pref_service.h"
#import "components/sync_preferences/testing_pref_service_syncable.h"
#import "ios/chrome/browser/default_browser/model/utils_test_support.h"
#import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
#import "ios/chrome/browser/shared/model/prefs/browser_prefs.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/test/testing_application_context.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
Expand All @@ -26,15 +33,15 @@
// More than 14 days.
constexpr base::TimeDelta kMoreThan14Days = base::Days(14) + base::Minutes(1);

// Less than 6 hours.
constexpr base::TimeDelta kLessThan6Hours = base::Hours(6) - base::Minutes(1);

// More than 6 hours.
constexpr base::TimeDelta kMoreThan6Hours = base::Hours(6) + base::Minutes(1);

// About 6 months.
constexpr base::TimeDelta k6Months = base::Days(6 * 365 / 12);

// About 1 year.
constexpr base::TimeDelta kMoreThan1Year = base::Days(365) + base::Days(1);

// About 2 years.
constexpr base::TimeDelta k2Years = base::Days(2 * 365);

Expand Down Expand Up @@ -73,10 +80,25 @@

class DefaultBrowserUtilsTest : public PlatformTest {
protected:
void SetUp() override { ClearDefaultBrowserPromoData(); }
void TearDown() override { ClearDefaultBrowserPromoData(); }
void SetUp() override {
PlatformTest::SetUp();
ClearDefaultBrowserPromoData();

local_state_ = std::make_unique<TestingPrefServiceSimple>();
RegisterLocalStatePrefs(local_state_->registry());
TestingApplicationContext::GetGlobal()->SetLocalState(local_state_.get());
}
void TearDown() override {
ClearDefaultBrowserPromoData();

TestingApplicationContext::GetGlobal()->SetLocalState(nullptr);
local_state_.reset();
PlatformTest::TearDown();
}

web::WebTaskEnvironment task_environment_;
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<TestingPrefServiceSimple> local_state_;
};

// Overwrite local storage with the provided interaction information.
Expand Down Expand Up @@ -268,31 +290,6 @@ void SimulateUserInteractionWithFullscreenPromo(const base::TimeDelta& timeAgo,
EXPECT_TRUE(HasUserInteractedWithTailoredFullscreenPromoBefore());
}

// Tests that a recent event timestamp (less than 6 hours) has already been
// recorded.
TEST_F(DefaultBrowserUtilsTest, HasRecentTimestampForKeyUnder6Hours) {
// Should return false at first, then true since an event has already been
// recorded when the second one is called.
EXPECT_FALSE(HasRecentTimestampForKey(kTestTimestampKey));
EXPECT_TRUE(HasRecentTimestampForKey(kTestTimestampKey));
}

// Manually tests that a recent event timestamp (less than 6 hours) has already
// been recorded.
TEST_F(DefaultBrowserUtilsTest, ManualHasRecentTimestampForKeyUnder6Hours) {
ResetStorageAndSetTimestampForKey(kTestTimestampKey,
(base::Time::Now() - kLessThan6Hours));
EXPECT_TRUE(HasRecentTimestampForKey(kTestTimestampKey));
}

// Manually tests that no recent event timestamp (more than 6 hours) has already
// been recorded.
TEST_F(DefaultBrowserUtilsTest, ManualRecentTimestampForKeyOver6Hours) {
ResetStorageAndSetTimestampForKey(kTestTimestampKey,
(base::Time::Now() - kMoreThan6Hours));
EXPECT_FALSE(HasRecentTimestampForKey(kTestTimestampKey));
}

// Tests that past interactions with the default browser promo are correctly
// detected when the sliding eligibility window experiment is disabled.
TEST_F(DefaultBrowserUtilsTest,
Expand Down Expand Up @@ -1086,4 +1083,45 @@ void SimulateUserInteractionWithFullscreenPromo(const base::TimeDelta& timeAgo,
EXPECT_TRUE(HasTriggerCriteriaExperimentStarted21days());
}

// Test that Blue dot display timestamp is recorded first time and is not
// updated afterwards.
TEST_F(DefaultBrowserUtilsTest, TestDefaultBrowserBlueDotFirstDisplay) {
EXPECT_FALSE(HasDefaultBrowserBlueDotDisplayTimestamp());

RecordDefaultBrowserBlueDotFirstDisplay();
EXPECT_TRUE(HasDefaultBrowserBlueDotDisplayTimestamp());

// Save current pref value and try calling
// `RecordDefaultBrowserBlueDotFirstDisplay` again.
base::Time timestamp =
local_state_->GetTime(prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay);
RecordDefaultBrowserBlueDotFirstDisplay();

// Get pref value again and check that it's same.
EXPECT_EQ(timestamp, local_state_->GetTime(
prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay));
}

// Test that timestamp will be reset when needed.
TEST_F(DefaultBrowserUtilsTest,
TestResetDefaultBrowserBlueDotDisplayTimestampIfNeeded) {
// It will not recent if the timestamp is less than 1 year old.
base::Time timestamp = base::Time::Now() - k6Months;
local_state_->SetTime(prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay,
timestamp);
ResetDefaultBrowserBlueDotDisplayTimestampIfNeeded();

// Check that didn't reset.
EXPECT_EQ(timestamp, local_state_->GetTime(
prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay));
EXPECT_TRUE(HasDefaultBrowserBlueDotDisplayTimestamp());

// Set the timestamp to over 1 year ago.
local_state_->SetTime(prefs::kIosDefaultBrowserBlueDotPromoFirstDisplay,
base::Time::Now() - kMoreThan1Year);
ResetDefaultBrowserBlueDotDisplayTimestampIfNeeded();

// Check that it got reset.
EXPECT_FALSE(HasDefaultBrowserBlueDotDisplayTimestamp());
}
} // namespace
Loading

0 comments on commit 1489924

Please sign in to comment.