blob: ac5923b4269491cfd2c4e8c9e37a46014bbfc4ae [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/promos/promos_utils.h"
#include <string>
#include <vector>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/promos/promos_pref_names.h"
#include "chrome/browser/promos/promos_types.h"
#include "chrome/common/pref_names.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/prefs/pref_service.h"
#include "components/segmentation_platform/embedder/default_model/device_switcher_model.h"
namespace promos_utils {
// TODO(crbug.com/339262105): Clean up the old password promo methods after the
// generic promo launch.
// Max impression count per user for the iOS password promo on desktop.
constexpr int kiOSPasswordPromoMaxImpressionCount = 2;
// TODO(crbug.com/339262105): Clean up the old password promo methods after the
// generic promo launch.
// Minimum time threshold between impressions for a given user to see the iOS
// password promo on desktop.
constexpr base::TimeDelta kiOSPasswordPromoCooldownTime = base::Days(60);
// Max impression count per user, per promo for the iOS desktop promos on
// desktop.
constexpr int kiOSDesktopPromoMaxImpressionCount = 3;
// Total impression count per user in their lifetime, for all iOS desktop
// promos.
constexpr int kiOSDesktopPromoTotalImpressionCount = 10;
// Total amount of opt-outs across any Desktop to iOS promo to block impressions
// of other instances of Desktop to iOS promos, per user.
constexpr int kiOSDesktopPromoTotalOptOuts = 2;
// Minimum time threshold between impressions for a given user to see the iOS
// desktop promo on desktop.
constexpr base::TimeDelta kiOSDesktopPromoCooldownTime = base::Days(90);
// IOSDesktopPromoHistogramType returns the promo histogram type for the given
// promo type.
std::string IOSDesktopPromoHistogramType(IOSPromoType promo_type) {
switch (promo_type) {
case IOSPromoType::kPassword:
return "PasswordPromo";
case IOSPromoType::kAddress:
return "AddressPromo";
// TODO(crbug.com/340269648): Add IOS Payment Promo for Desktop.
default:
NOTREACHED();
}
}
// VerifyIOSDesktopPromoTotalImpressions ensures that each individual user sees
// 10 of these promos total in their lifetime.
bool VerifyIOSDesktopPromoTotalImpressions(Profile* profile) {
// TODO(crbug.com/339262105): Add new promos to this check.
int total_desktop_promo_impressions =
profile->GetPrefs()->GetInteger(
promos_prefs::kiOSPasswordPromoImpressionsCounter) +
profile->GetPrefs()->GetInteger(
promos_prefs::kDesktopToiOSAddressPromoImpressionsCounter);
return total_desktop_promo_impressions <=
kiOSDesktopPromoTotalImpressionCount;
}
// VerifyIOSDesktopPromoTotalOptOuts verifies that a user hasn't opted-out of
// seeing more than 1 of the 4 promo types (Passwords, Bookmarks, Addresses,
// Payments).
bool VerifyIOSDesktopPromoTotalOptOuts(Profile* profile) {
// TODO(crbug.com/339262105): Add new promos to this check.
std::vector<bool> promo_opt_outs = {
profile->GetPrefs()->GetBoolean(promos_prefs::kiOSPasswordPromoOptOut),
profile->GetPrefs()->GetBoolean(
promos_prefs::kDesktopToiOSAddressPromoOptOut)};
int total_desktop_promo_opt_outs_counter =
std::count(promo_opt_outs.begin(), promo_opt_outs.end(), true);
return total_desktop_promo_opt_outs_counter < kiOSDesktopPromoTotalOptOuts;
}
// VerifyMostRecentPromoTimestamp ensures that each individual user sees a
// iOS to Desktop promo a maximum of once every 90 days.
bool VerifyMostRecentPromoTimestamp(Profile* profile) {
// TODO(crbug.com/339262105): Add new promos to this check.
std::vector<base::Time> promos_timestamps;
promos_timestamps.push_back(profile->GetPrefs()->GetTime(
promos_prefs::kiOSPasswordPromoLastImpressionTimestamp));
promos_timestamps.push_back(profile->GetPrefs()->GetTime(
promos_prefs::kDesktopToiOSAddressPromoLastImpressionTimestamp));
auto most_recent_promo_timestamp =
std::max_element(promos_timestamps.begin(), promos_timestamps.end());
return *most_recent_promo_timestamp + kiOSDesktopPromoCooldownTime <
base::Time::Now();
}
// TODO(crbug.com/339262105): Clean up the old password promo methods after
// the generic promo launch.
// RecordIOSPasswordPromoShownHistogram records which impression (count) was
// shown to the user.
void RecordIOSPasswordPromoShownHistogram(int impression_count) {
if (impression_count == 1) {
base::UmaHistogramEnumeration(
"IOS.DesktopPasswordPromo.Shown",
promos_utils::DesktopIOSPasswordPromoImpression::kFirstImpression);
} else if (impression_count == 2) {
base::UmaHistogramEnumeration(
"IOS.DesktopPasswordPromo.Shown",
promos_utils::DesktopIOSPasswordPromoImpression::kSecondImpression);
} else {
NOTREACHED();
}
}
// RecordIOSDesktopPromoShownHistogram records which impression (count) was
// shown to the user depending on the given promo type.
void RecordIOSDesktopPromoShownHistogram(IOSPromoType promo_type,
int impression_count) {
std::string promo_histogram_type = IOSDesktopPromoHistogramType(promo_type);
DesktopIOSPromoImpression promo_impression;
switch (impression_count) {
case 1:
promo_impression = DesktopIOSPromoImpression::kFirstImpression;
break;
case 2:
promo_impression = DesktopIOSPromoImpression::kSecondImpression;
break;
case 3:
promo_impression = DesktopIOSPromoImpression::kThirdImpression;
break;
default:
NOTREACHED();
}
base::UmaHistogramEnumeration(
"IOS.Desktop." + promo_histogram_type + ".Shown", promo_impression);
}
// IOSPromoPrefsConfig is a complex struct that needs definition of a
// constructor, an explicit out-of-line copy constructor and a destructor.
IOSPromoPrefsConfig::IOSPromoPrefsConfig() = default;
IOSPromoPrefsConfig::IOSPromoPrefsConfig(const IOSPromoPrefsConfig&) = default;
IOSPromoPrefsConfig::~IOSPromoPrefsConfig() = default;
IOSPromoPrefsConfig::IOSPromoPrefsConfig(IOSPromoType promo_type) {
switch (promo_type) {
case IOSPromoType::kPassword:
// This feature isn't defined with those buildflags.
#if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
promo_feature = &feature_engagement::kIPHiOSPasswordPromoDesktopFeature;
#endif
promo_impressions_counter_pref_name =
promos_prefs::kiOSPasswordPromoImpressionsCounter;
promo_opt_out_pref_name = promos_prefs::kiOSPasswordPromoOptOut;
promo_last_impression_timestamp_pref_name =
promos_prefs::kiOSPasswordPromoLastImpressionTimestamp;
break;
case IOSPromoType::kAddress:
// This feature isn't defined with those buildflags.
#if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
promo_feature = &feature_engagement::kIPHiOSAddressPromoDesktopFeature;
#endif
promo_impressions_counter_pref_name =
promos_prefs::kDesktopToiOSAddressPromoImpressionsCounter;
promo_opt_out_pref_name = promos_prefs::kDesktopToiOSAddressPromoOptOut;
promo_last_impression_timestamp_pref_name =
promos_prefs::kDesktopToiOSAddressPromoLastImpressionTimestamp;
break;
// TODO(crbug.com/340269648): Add IOS Payment Promo for Desktop.
default:
NOTREACHED();
}
}
void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterTimePref(
promos_prefs::kiOSPasswordPromoLastImpressionTimestamp, base::Time(),
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterIntegerPref(
promos_prefs::kiOSPasswordPromoImpressionsCounter, 0,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterBooleanPref(
promos_prefs::kiOSPasswordPromoOptOut, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterTimePref(
promos_prefs::kDesktopToiOSAddressPromoLastImpressionTimestamp,
base::Time(), user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterIntegerPref(
promos_prefs::kDesktopToiOSAddressPromoImpressionsCounter, 0,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterBooleanPref(
promos_prefs::kDesktopToiOSAddressPromoOptOut, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
}
// TODO(crbug.com/339262105): Clean up the old password promo methods after
// the generic promo launch.
void RecordIOSPasswordPromoUserInteractionHistogram(
int impression_count,
DesktopIOSPasswordPromoAction action) {
if (impression_count == 1) {
base::UmaHistogramEnumeration(
"IOS.DesktopPasswordPromo.FirstImpression.Action", action);
} else if (impression_count == 2) {
base::UmaHistogramEnumeration(
"IOS.DesktopPasswordPromo.SecondImpression.Action", action);
} else {
NOTREACHED();
}
}
// RecordIOSDesktopPromoUserInteractionHistogram records which impression
// (count) depending on the promo type.
void RecordIOSDesktopPromoUserInteractionHistogram(
IOSPromoType promo_type,
int impression_count,
DesktopIOSPromoAction action) {
std::string promo_histogram_type = IOSDesktopPromoHistogramType(promo_type);
if (impression_count == 1) {
base::UmaHistogramEnumeration(
"IOS.Desktop." + promo_histogram_type + ".FirstImpression.Action",
action);
} else if (impression_count == 2) {
base::UmaHistogramEnumeration(
"IOS.Desktop." + promo_histogram_type + ".SecondImpression.Action",
action);
} else if (impression_count == 3) {
base::UmaHistogramEnumeration(
"IOS.Desktop." + promo_histogram_type + ".ThirdImpression.Action",
action);
} else {
NOTREACHED();
}
}
// TODO(crbug.com/339262105): Clean up the old password promo methods after
// the generic promo launch.
bool ShouldShowIOSPasswordPromo(Profile* profile) {
// Show the promo if the user hasn't opted out, is not in the cooldown
// period and is within the impression limit for this promo.
if (profile->GetPrefs()->GetInteger(
promos_prefs::kiOSPasswordPromoImpressionsCounter) <
kiOSPasswordPromoMaxImpressionCount &&
profile->GetPrefs()->GetTime(
promos_prefs::kiOSPasswordPromoLastImpressionTimestamp) +
kiOSPasswordPromoCooldownTime <
base::Time::Now() &&
!profile->GetPrefs()->GetBoolean(promos_prefs::kiOSPasswordPromoOptOut)) {
return true;
}
return false;
}
bool ShouldShowIOSDesktopPromo(Profile* profile, IOSPromoType promo_type) {
// Don't show the promo if the local state exists and `kPromotionsEnabled` is
// false (likely overridden by policy).
#if !BUILDFLAG(IS_ANDROID)
PrefService* local_state = g_browser_process->local_state();
if (local_state && !local_state->GetBoolean(prefs::kPromotionsEnabled)) {
return false;
}
#endif // !BUILDFLAG(IS_ANDROID)
IOSPromoPrefsConfig promo_prefs(promo_type);
// Show the promo if the user hasn't opted out, is not in the cooldown
// period and is within the impression limit for this promo.
return profile->GetPrefs()->GetInteger(
promo_prefs.promo_impressions_counter_pref_name) <
kiOSDesktopPromoMaxImpressionCount &&
// TODO(crbug.com/339262105): Add new promos to these checks.
VerifyMostRecentPromoTimestamp(profile) &&
VerifyIOSDesktopPromoTotalImpressions(profile) &&
VerifyIOSDesktopPromoTotalOptOuts(profile) &&
!profile->GetPrefs()->GetBoolean(promo_prefs.promo_opt_out_pref_name);
}
bool UserNotClassifiedAsMobileDeviceSwitcher(
const segmentation_platform::ClassificationResult& result) {
return result.status == segmentation_platform::PredictionStatus::kSucceeded &&
!base::Contains(
result.ordered_labels,
segmentation_platform::DeviceSwitcherModel::kAndroidPhoneLabel) &&
!base::Contains(result.ordered_labels,
segmentation_platform::DeviceSwitcherModel::
kIosPhoneChromeLabel) &&
!base::Contains(
result.ordered_labels,
segmentation_platform::DeviceSwitcherModel::kAndroidTabletLabel) &&
!base::Contains(
result.ordered_labels,
segmentation_platform::DeviceSwitcherModel::kIosTabletLabel);
}
// TODO(crbug.com/339262105): Clean up the old password promo methods after
// the generic promo launch.
void iOSPasswordPromoShown(Profile* profile) {
int new_impression_count =
profile->GetPrefs()->GetInteger(
promos_prefs::kiOSPasswordPromoImpressionsCounter) +
1;
profile->GetPrefs()->SetInteger(
promos_prefs::kiOSPasswordPromoImpressionsCounter, new_impression_count);
profile->GetPrefs()->SetTime(
promos_prefs::kiOSPasswordPromoLastImpressionTimestamp,
base::Time::Now());
RecordIOSPasswordPromoShownHistogram(new_impression_count);
}
void IOSDesktopPromoShown(Profile* profile, IOSPromoType promo_type) {
IOSPromoPrefsConfig promo_prefs(promo_type);
int new_impression_count =
profile->GetPrefs()->GetInteger(
promo_prefs.promo_impressions_counter_pref_name) +
1;
profile->GetPrefs()->SetInteger(
promo_prefs.promo_impressions_counter_pref_name, new_impression_count);
profile->GetPrefs()->SetTime(
promo_prefs.promo_last_impression_timestamp_pref_name, base::Time::Now());
RecordIOSDesktopPromoShownHistogram(promo_type, new_impression_count);
}
} // namespace promos_utils