blob: c99cc9c2d5d48c118e6801372c3959f6f3212f68 [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.
#import "ios/chrome/browser/default_browser/utils.h"
#import "base/command_line.h"
#import "base/ios/ios_util.h"
#import "base/mac/foundation_util.h"
#import "base/metrics/field_trial_params.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/notreached.h"
#import "base/strings/string_number_conversions.h"
#import "base/time/time.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/browser/feature_engagement/tracker_factory.h"
#import "ios/chrome/browser/ntp/features.h"
#import "ios/chrome/browser/settings/sync/utils/identity_error_util.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import <UIKit/UIKit.h>
// Key in NSUserDefaults containing an NSDictionary used to store all the
// information.
extern NSString* const kDefaultBrowserUtilsKey;
namespace {
// Key in storage containing an NSDate corresponding to the last time
// an HTTP(S) link was sent and opened by the app.
NSString* const kLastHTTPURLOpenTime = @"lastHTTPURLOpenTime";
// Key in storage containing an array of dates. Each date correspond to
// a general event of interest for Default Browser Promo modals.
NSString* const kLastSignificantUserEventGeneral = @"lastSignificantUserEvent";
// Key in storage containing an array of dates. Each date correspond to
// a stay safe event of interest for Default Browser Promo modals.
NSString* const kLastSignificantUserEventStaySafe =
@"lastSignificantUserEventStaySafe";
// Key in storage containing an array of dates. Each date correspond to
// a made for iOS event of interest for Default Browser Promo modals.
NSString* const kLastSignificantUserEventMadeForIOS =
@"lastSignificantUserEventMadeForIOS";
// Key in storage containing an array of dates. Each date correspond to
// an all tabs event of interest for Default Browser Promo modals.
NSString* const kLastSignificantUserEventAllTabs =
@"lastSignificantUserEventAllTabs";
// Key in storage containing an array of dates. Each date correspond to
// a video event of interest for Default Browser Promo modals.
NSString* const kLastSignificantUserEventVideo =
@"lastSignificantUserEventVideo";
// Key in storage containing an NSDate indicating the last time a user
// interacted with ANY promo. The string value is kept from when the promos
// first launched to avoid changing the behavior for users that have already
// seen the promo.
NSString* const kLastTimeUserInteractedWithPromo =
@"lastTimeUserInteractedWithFullscreenPromo";
// Key in storage containing a bool indicating if the user has
// previously interacted with a regular fullscreen promo.
NSString* const kUserHasInteractedWithFullscreenPromo =
@"userHasInteractedWithFullscreenPromo";
// Key in storage containing a bool indicating if the user has
// previously interacted with a tailored fullscreen promo.
NSString* const kUserHasInteractedWithTailoredFullscreenPromo =
@"userHasInteractedWithTailoredFullscreenPromo";
// Key in storage containing a bool indicating if the user has
// previously interacted with first run promo.
NSString* const kUserHasInteractedWithFirstRunPromo =
@"userHasInteractedWithFirstRunPromo";
// Key in storage containing an int indicating the number of times the
// user has interacted with a non-modal promo.
NSString* const kUserInteractedWithNonModalPromoCount =
@"userInteractedWithNonModalPromoCount";
// Key in storage containing an int indicating the number of times a
// promo has been displayed.
NSString* const kDisplayedPromoCount = @"displayedPromoCount";
// TODO(crbug.com/1445218): Remove in M116+.
// Key in storage containing an NSDate indicating the last time a user
// interacted with the "remind me later" panel.
NSString* const kRemindMeLaterPromoActionInteraction =
@"remindMeLaterPromoActionInteraction";
// TODO(crbug.com/1445240): Remove in M116+.
// Key in storage containing a bool indicating if the user tapped on
// button to open settings.
NSString* const kOpenSettingsActionInteraction =
@"openSettingsActionInteraction";
// Key in storage containing the timestamp of the last time the user opened the
// app via first-party intent.
NSString* const kTimestampAppLastOpenedViaFirstPartyIntent =
@"TimestampAppLastOpenedViaFirstPartyIntent";
// Key in storage containing the timestamp of the last time the user pasted a
// valid URL into the omnibox.
NSString* const kTimestampLastValidURLPasted = @"TimestampLastValidURLPasted";
// Key in storage containing the timestamp of the last time the user opened the
// app via first-party intent.
NSString* const kTimestampAppLaunchOnColdStart =
@"TimestampAppLaunchedOnColdStart";
const char kDefaultBrowserPromoForceShowPromo[] =
"default-browser-promo-force-show-promo";
// Maximum number of past event timestamps to record.
const size_t kMaxPastTimestampsToRecord = 10;
// Time threshold before activity timestamps should be removed.
constexpr base::TimeDelta kUserActivityTimestampExpiration = base::Days(21);
// Time threshold for the last URL open before no URL opens likely indicates
// Chrome is no longer the default browser.
constexpr base::TimeDelta kLatestURLOpenForDefaultBrowser = base::Days(21);
// Cool down between fullscreen promos.
constexpr base::TimeDelta kFullscreenPromoCoolDown = base::Days(14);
// Short cool down between promos.
constexpr base::TimeDelta kPromosShortCoolDown = base::Days(3);
// Maximum time range between first-party app launches to notify the FET.
constexpr base::TimeDelta kMaximumTimeBetweenFirstPartyAppLaunches =
base::Days(7);
// Maximum time range between app launches on cold start to notify the FET.
constexpr base::TimeDelta kMaximumTimeBetweenAppColdStartLaunches =
base::Days(7);
// Maximum time range between valid user URL pastes to notify the FET.
constexpr base::TimeDelta kMaximumTimeBetweenValidURLPastes = base::Days(7);
// List of DefaultPromoType considered by MostRecentInterestDefaultPromoType.
const DefaultPromoType kDefaultPromoTypes[] = {
DefaultPromoTypeStaySafe,
DefaultPromoTypeAllTabs,
DefaultPromoTypeMadeForIOS,
};
// Creates storage object from legacy keys.
NSMutableDictionary<NSString*, NSObject*>* CreateStorageObjectFromLegacyKeys() {
NSMutableDictionary<NSString*, NSObject*>* dictionary =
[[NSMutableDictionary alloc] init];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
for (NSString* key in DefaultBrowserUtilsLegacyKeysForTesting()) {
NSObject* object = [defaults objectForKey:key];
if (object) {
dictionary[key] = object;
[defaults removeObjectForKey:key];
}
}
return dictionary;
}
// Helper function to get the data for `key` from the storage object.
template <typename T>
T* GetObjectFromStorageForKey(NSString* key) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSDictionary<NSString*, NSObject*>* storage =
[defaults objectForKey:kDefaultBrowserUtilsKey];
// If the storage is missing, create it, possibly from the legacy keys.
// This is used to support loading data written by version 109 or ealier.
// Remove once migrating data from such old version is no longer supported.
if (!storage) {
storage = CreateStorageObjectFromLegacyKeys();
[defaults setObject:storage forKey:kDefaultBrowserUtilsKey];
}
DCHECK(storage);
return base::mac::ObjCCast<T>(storage[key]);
}
// Helper function to update storage with `dict`. If a key in `dict` maps
// to `NSNull` instance, it will be removed from storage.
void UpdateStorageWithDictionary(NSDictionary<NSString*, NSObject*>* dict) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary<NSString*, NSObject*>* storage =
[[defaults objectForKey:kDefaultBrowserUtilsKey] mutableCopy];
// If the storage is missing, create it, possibly from the legacy keys.
// This is used to support loading data written by version 109 or ealier.
// Remove once migrating data from such old version is no longer supported.
if (!storage) {
storage = CreateStorageObjectFromLegacyKeys();
}
DCHECK(storage);
for (NSString* key in dict) {
NSObject* object = dict[key];
if (object == [NSNull null]) {
[storage removeObjectForKey:key];
} else {
storage[key] = object;
}
}
[defaults setObject:storage forKey:kDefaultBrowserUtilsKey];
}
// Helper function to set `data` for `key` into the storage object.
void SetObjectIntoStorageForKey(NSString* key, NSObject* data) {
UpdateStorageWithDictionary(@{key : data});
}
// Helper function to get the storage key for a specific promo type.
NSString* StorageKeyForDefaultPromoType(DefaultPromoType type) {
switch (type) {
case DefaultPromoTypeGeneral:
return kLastSignificantUserEventGeneral;
case DefaultPromoTypeMadeForIOS:
return kLastSignificantUserEventMadeForIOS;
case DefaultPromoTypeAllTabs:
return kLastSignificantUserEventAllTabs;
case DefaultPromoTypeStaySafe:
return kLastSignificantUserEventStaySafe;
case DefaultPromoTypeVideo:
return kLastSignificantUserEventVideo;
}
NOTREACHED();
return nil;
}
// Loads from NSUserDefaults the time of the last non-expired events.
std::vector<base::Time> LoadTimestampsForPromoType(DefaultPromoType type) {
NSString* key = StorageKeyForDefaultPromoType(type);
NSArray* dates = GetObjectFromStorageForKey<NSArray>(key);
if (!dates) {
return {};
}
std::vector<base::Time> times;
times.reserve(dates.count);
const base::Time now = base::Time::Now();
for (NSObject* object : dates) {
NSDate* date = base::mac::ObjCCast<NSDate>(object);
if (!date) {
continue;
}
const base::Time time = base::Time::FromNSDate(date);
if (now - time > kUserActivityTimestampExpiration) {
continue;
}
times.push_back(time);
}
return times;
}
// Stores the time of the last recorded events for `type`.
void StoreTimestampsForPromoType(DefaultPromoType type,
std::vector<base::Time> times) {
NSMutableArray<NSDate*>* dates =
[[NSMutableArray alloc] initWithCapacity:times.size()];
// Only record up to kMaxPastTimestampsToRecord timestamps.
if (times.size() > kMaxPastTimestampsToRecord) {
const size_t count_to_erase = times.size() - kMaxPastTimestampsToRecord;
times.erase(times.begin(), times.begin() + count_to_erase);
}
for (base::Time time : times) {
[dates addObject:time.ToNSDate()];
}
NSString* key = StorageKeyForDefaultPromoType(type);
SetObjectIntoStorageForKey(key, dates);
}
// Returns whether an event was logged for key occuring less than `delay`
// in the past.
bool HasRecordedEventForKeyLessThanDelay(NSString* key, base::TimeDelta delay) {
NSDate* date = GetObjectFromStorageForKey<NSDate>(key);
if (!date) {
return false;
}
const base::Time time = base::Time::FromNSDate(date);
return base::Time::Now() - time < delay;
}
// Returns whether an event was logged for key occuring more than `delay`
// in the past.
bool HasRecordedEventForKeyMoreThanDelay(NSString* key, base::TimeDelta delay) {
NSDate* date = GetObjectFromStorageForKey<NSDate>(key);
if (!date) {
return false;
}
const base::Time time = base::Time::FromNSDate(date);
return base::Time::Now() - time > delay;
}
// `YES` if user interacted with the first run default browser screen.
BOOL HasUserInteractedWithFirstRunPromoBefore() {
NSNumber* number =
GetObjectFromStorageForKey<NSNumber>(kUserHasInteractedWithFirstRunPromo);
return number.boolValue;
}
// Returns the number of time a default browser promo has been displayed.
NSInteger DisplayedPromoCount() {
NSNumber* number = GetObjectFromStorageForKey<NSNumber>(kDisplayedPromoCount);
return number.integerValue;
}
// Computes cool down between promos.
base::TimeDelta ComputeCooldown() {
// `true` if the user is in the short delay group experiment and tap on the
// "No thanks" button in first run default browser screen. Short cool down
// should be set only one time, so after the first run promo there is a short
// cool down before the next promo and after it goes back to normal.
if (DisplayedPromoCount() < 2 && HasUserInteractedWithFirstRunPromoBefore()) {
return kPromosShortCoolDown;
}
return kFullscreenPromoCoolDown;
}
} // namespace
void LogOpenHTTPURLFromExternalURL() {
SetObjectIntoStorageForKey(kLastHTTPURLOpenTime, [NSDate date]);
}
void LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoType type) {
std::vector<base::Time> times = LoadTimestampsForPromoType(type);
times.push_back(base::Time::Now());
StoreTimestampsForPromoType(type, std::move(times));
}
void LogToFETDefaultBrowserPromoShown(feature_engagement::Tracker* tracker) {
// OTR browsers can sometimes pass a null tracker, check for that here.
if (!tracker) {
return;
}
tracker->NotifyEvent(feature_engagement::events::kDefaultBrowserPromoShown);
}
void LogToFETUserPastedURLIntoOmnibox(feature_engagement::Tracker* tracker) {
base::RecordAction(
base::UserMetricsAction("Mobile.Omnibox.iOS.PastedValidURL"));
// OTR browsers can sometimes pass a null tracker, check for that here.
if (!tracker) {
return;
}
if (HasRecentValidURLPastesAndRecordsCurrentPaste()) {
tracker->NotifyEvent(feature_engagement::events::kBlueDotPromoCriterionMet);
if (IsDefaultBrowserVideoPromoEnabled()) {
tracker->NotifyEvent(
feature_engagement::events::kDefaultBrowserVideoPromoConditionsMet);
}
}
}
bool ShouldTriggerDefaultBrowserHighlightFeature(
const base::Feature& feature,
feature_engagement::Tracker* tracker,
syncer::SyncService* syncService) {
// TODO(crbug.com/1410229) clean-up experiment code when fully launched.
if (!IsBlueDotPromoEnabled() || IsChromeLikelyDefaultBrowser() ||
(syncService && ShouldIndicateIdentityErrorInOverflowMenu(syncService))) {
return false;
}
// We need to ask the FET whether or not we should show this IPH because if
// yes, this will automatically notify the other dependent FET features that
// their criteria have been met. We then automatically dismiss it. Since it's
// just a shadow feature to enable the other two needed for the blue dot
// promo, we ignore `ShouldTriggerHelpUI`'s return value.
if (tracker->ShouldTriggerHelpUI(
feature_engagement::kIPHiOSDefaultBrowserBadgeEligibilityFeature)) {
tracker->Dismissed(
feature_engagement::kIPHiOSDefaultBrowserBadgeEligibilityFeature);
}
// Now, we ask the appropriate FET feature if it should trigger, i.e. if we
// should show the blue dot promo badge.
if (tracker->ShouldTriggerHelpUI(feature)) {
tracker->Dismissed(feature);
return true;
}
return false;
}
bool AreDefaultBrowserPromosEnabled() {
if (base::FeatureList::IsEnabled(kDefaultBrowserBlueDotPromo)) {
return kBlueDotPromoUserGroupParam.Get() ==
BlueDotPromoUserGroup::kAllDBPromosEnabled;
}
return true;
}
bool IsBlueDotPromoEnabled() {
if (base::FeatureList::IsEnabled(kDefaultBrowserBlueDotPromo)) {
return kBlueDotPromoUserGroupParam.Get() ==
BlueDotPromoUserGroup::kOnlyBlueDotPromoEnabled ||
kBlueDotPromoUserGroupParam.Get() ==
BlueDotPromoUserGroup::kAllDBPromosEnabled;
}
return false;
}
bool IsDefaultBrowserInPromoManagerEnabled() {
return base::FeatureList::IsEnabled(kDefaultBrowserRefactoringPromoManager);
}
bool IsDefaultBrowserVideoPromoEnabled() {
return base::FeatureList::IsEnabled(kDefaultBrowserVideoPromo);
}
bool ShouldForceDefaultPromoType() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
kDefaultBrowserPromoForceShowPromo);
}
DefaultPromoType ForceDefaultPromoType() {
DCHECK(ShouldForceDefaultPromoType());
std::string type =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kDefaultBrowserPromoForceShowPromo);
int default_promo_type = 0;
if (base::StringToInt(type, &default_promo_type)) {
switch (default_promo_type) {
case DefaultPromoTypeGeneral:
case DefaultPromoTypeStaySafe:
case DefaultPromoTypeMadeForIOS:
case DefaultPromoTypeAllTabs:
case DefaultPromoTypeVideo:
return static_cast<DefaultPromoType>(default_promo_type);
}
}
return DefaultPromoType::DefaultPromoTypeGeneral;
}
bool IsDefaultBrowserVideoPromoHalfscreenEnabled() {
return base::GetFieldTrialParamByFeatureAsBool(
kDefaultBrowserVideoPromo, "default_browser_video_promo_halfscreen",
false);
}
bool HasUserInteractedWithFullscreenPromoBefore() {
NSNumber* number = GetObjectFromStorageForKey<NSNumber>(
kUserHasInteractedWithFullscreenPromo);
return number.boolValue;
}
bool HasUserInteractedWithTailoredFullscreenPromoBefore() {
NSNumber* number = GetObjectFromStorageForKey<NSNumber>(
kUserHasInteractedWithTailoredFullscreenPromo);
return number.boolValue;
}
NSInteger UserInteractionWithNonModalPromoCount() {
NSNumber* number = GetObjectFromStorageForKey<NSNumber>(
kUserInteractedWithNonModalPromoCount);
return number.integerValue;
}
void LogDefaultBrowserPromoDisplayed() {
const NSInteger displayed_promo_count = DisplayedPromoCount();
NSDictionary<NSString*, NSObject*>* update = @{
kDisplayedPromoCount : @(displayed_promo_count + 1),
};
UpdateStorageWithDictionary(update);
}
void LogUserInteractionWithFullscreenPromo() {
NSDictionary<NSString*, NSObject*>* update = @{
kUserHasInteractedWithFullscreenPromo : @YES,
kLastTimeUserInteractedWithPromo : [NSDate date],
};
UpdateStorageWithDictionary(update);
}
void LogUserInteractionWithTailoredFullscreenPromo() {
UpdateStorageWithDictionary(@{
kUserHasInteractedWithTailoredFullscreenPromo : @YES,
kLastTimeUserInteractedWithPromo : [NSDate date],
});
}
void LogUserInteractionWithNonModalPromo() {
const NSInteger interaction_count = UserInteractionWithNonModalPromoCount();
const NSInteger displayed_promo_count = DisplayedPromoCount();
UpdateStorageWithDictionary(@{
kLastTimeUserInteractedWithPromo : [NSDate date],
kUserInteractedWithNonModalPromoCount : @(interaction_count + 1),
kDisplayedPromoCount : @(displayed_promo_count + 1),
});
}
void LogUserInteractionWithFirstRunPromo(BOOL openedSettings) {
const NSInteger displayed_promo_count = DisplayedPromoCount();
UpdateStorageWithDictionary(@{
kUserHasInteractedWithFirstRunPromo : @YES,
kLastTimeUserInteractedWithPromo : [NSDate date],
kDisplayedPromoCount : @(displayed_promo_count + 1),
});
}
bool HasRecentFirstPartyIntentLaunchesAndRecordsCurrentLaunch() {
const base::TimeDelta max_session_time =
base::Seconds(GetFeedUnseenRefreshThresholdInSeconds());
if (HasRecordedEventForKeyLessThanDelay(
kTimestampAppLastOpenedViaFirstPartyIntent,
kMaximumTimeBetweenFirstPartyAppLaunches)) {
if (HasRecordedEventForKeyMoreThanDelay(
kTimestampAppLastOpenedViaFirstPartyIntent, max_session_time)) {
SetObjectIntoStorageForKey(kTimestampAppLastOpenedViaFirstPartyIntent,
[NSDate date]);
return YES;
}
return NO;
}
SetObjectIntoStorageForKey(kTimestampAppLastOpenedViaFirstPartyIntent,
[NSDate date]);
return NO;
}
bool HasRecentValidURLPastesAndRecordsCurrentPaste() {
if (HasRecordedEventForKeyLessThanDelay(kTimestampLastValidURLPasted,
kMaximumTimeBetweenValidURLPastes)) {
SetObjectIntoStorageForKey(kTimestampLastValidURLPasted, [NSDate date]);
return YES;
}
SetObjectIntoStorageForKey(kTimestampLastValidURLPasted, [NSDate date]);
return NO;
}
bool HasRecentTimestampForKey(NSString* eventKey) {
const base::TimeDelta max_session_time =
base::Seconds(GetFeedUnseenRefreshThresholdInSeconds());
if (HasRecordedEventForKeyLessThanDelay(eventKey, max_session_time)) {
return YES;
}
SetObjectIntoStorageForKey(eventKey, [NSDate date]);
return NO;
}
bool IsChromeLikelyDefaultBrowser7Days() {
return HasRecordedEventForKeyLessThanDelay(kLastHTTPURLOpenTime,
base::Days(7));
}
bool IsChromeLikelyDefaultBrowser() {
return HasRecordedEventForKeyLessThanDelay(kLastHTTPURLOpenTime,
kLatestURLOpenForDefaultBrowser);
}
bool IsLikelyInterestedDefaultBrowserUser(DefaultPromoType promo_type) {
std::vector<base::Time> times = LoadTimestampsForPromoType(promo_type);
return !times.empty();
}
DefaultPromoType MostRecentInterestDefaultPromoType(
BOOL skip_all_tabs_promo_type) {
DefaultPromoType most_recent_event_type = DefaultPromoTypeGeneral;
base::Time most_recent_event_time = base::Time::Min();
for (DefaultPromoType promo_type : kDefaultPromoTypes) {
// Ignore DefaultPromoTypeAllTabs if the extra requirements are not met.
if (promo_type == DefaultPromoTypeAllTabs && skip_all_tabs_promo_type) {
continue;
}
std::vector<base::Time> times = LoadTimestampsForPromoType(promo_type);
if (times.empty()) {
continue;
}
const base::Time last_time_for_type = times.back();
if (last_time_for_type >= most_recent_event_time) {
most_recent_event_type = promo_type;
most_recent_event_time = last_time_for_type;
}
}
return most_recent_event_type;
}
bool UserInPromoCooldown() {
return HasRecordedEventForKeyLessThanDelay(kLastTimeUserInteractedWithPromo,
ComputeCooldown());
}
// Visible for testing.
NSString* const kDefaultBrowserUtilsKey = @"DefaultBrowserUtils";
// Visible for testing.
const NSArray<NSString*>* DefaultBrowserUtilsLegacyKeysForTesting() {
NSArray<NSString*>* const keysForTesting = @[
// clang-format off
kLastHTTPURLOpenTime,
kLastSignificantUserEventGeneral,
kLastSignificantUserEventStaySafe,
kLastSignificantUserEventMadeForIOS,
kLastSignificantUserEventAllTabs,
kLastTimeUserInteractedWithPromo,
kUserHasInteractedWithFullscreenPromo,
kUserHasInteractedWithTailoredFullscreenPromo,
kUserHasInteractedWithFirstRunPromo,
kUserInteractedWithNonModalPromoCount,
kDisplayedPromoCount,
kRemindMeLaterPromoActionInteraction,
// clang-format on
];
return keysForTesting;
}
bool HasAppLaunchedOnColdStartAndRecordsLaunch() {
if (HasRecordedEventForKeyLessThanDelay(
kTimestampAppLaunchOnColdStart,
kMaximumTimeBetweenAppColdStartLaunches)) {
SetObjectIntoStorageForKey(kTimestampAppLaunchOnColdStart, [NSDate date]);
return YES;
}
// Add a new timestamp if the timestamp was never recorded or if it was
// recorded more than the maximum time between app cold starts.
SetObjectIntoStorageForKey(kTimestampAppLaunchOnColdStart, [NSDate date]);
return NO;
}
bool ShouldRegisterPromoWithPromoManager(bool is_signed_in,
feature_engagement::Tracker* tracker) {
if (ShouldForceDefaultPromoType()) {
return YES;
}
// Consider showing the default browser promo if (1) launch is not after a
// crash, (2) chrome is not likely set as default browser, (3) the user has
// not seen a default browser promo too recently, (4) the user is eligible
// for either the tailored or generic default browser promo.
return GetApplicationContext()->WasLastShutdownClean() &&
!IsChromeLikelyDefaultBrowser() && !UserInPromoCooldown() &&
(IsTailoredPromoEligibleUser(is_signed_in) ||
IsGeneralPromoEligibleUser(is_signed_in) ||
IsVideoPromoEligibleUser(tracker));
}
bool IsTailoredPromoEligibleUser(bool is_signed_in) {
bool is_made_for_ios_promo_eligible =
IsLikelyInterestedDefaultBrowserUser(DefaultPromoTypeMadeForIOS);
bool is_all_tabs_promo_eligible =
IsLikelyInterestedDefaultBrowserUser(DefaultPromoTypeAllTabs) &&
is_signed_in;
bool is_stay_safe_promo_eligible =
IsLikelyInterestedDefaultBrowserUser(DefaultPromoTypeStaySafe);
return !HasUserInteractedWithTailoredFullscreenPromoBefore() &&
(is_made_for_ios_promo_eligible || is_all_tabs_promo_eligible ||
is_stay_safe_promo_eligible);
}
bool IsGeneralPromoEligibleUser(bool is_signed_in) {
return !HasUserInteractedWithFullscreenPromoBefore() &&
(IsLikelyInterestedDefaultBrowserUser(DefaultPromoTypeGeneral) ||
is_signed_in);
}
bool IsVideoPromoEligibleUser(feature_engagement::Tracker* tracker) {
if (!IsDefaultBrowserVideoPromoEnabled()) {
return false;
}
if (!tracker ||
!tracker->WouldTriggerHelpUI(
feature_engagement::kIPHiOSDefaultBrowserVideoPromoTriggerFeature)) {
return false;
}
return true;
}
void CleanupUnusedStorage() {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
// TODO(crbug.com/1445240): Remove in M116+.
[defaults removeObjectForKey:kOpenSettingsActionInteraction];
// TODO(crbug.com/1445218): Remove in M116+.
[defaults removeObjectForKey:kRemindMeLaterPromoActionInteraction];
}
DefaultPromoTypeForUMA GetDefaultPromoTypeForUMA(DefaultPromoType type) {
switch (type) {
case DefaultPromoTypeGeneral:
return DefaultPromoTypeForUMA::kGeneral;
case DefaultPromoTypeMadeForIOS:
return DefaultPromoTypeForUMA::kMadeForIOS;
case DefaultPromoTypeStaySafe:
return DefaultPromoTypeForUMA::kStaySafe;
case DefaultPromoTypeAllTabs:
return DefaultPromoTypeForUMA::kAllTabs;
default:
NOTREACHED_NORETURN();
}
}
void LogDefaultBrowserPromoHistogramForAction(
DefaultPromoType type,
IOSDefaultBrowserPromoAction action) {
switch (type) {
case DefaultPromoTypeGeneral:
base::UmaHistogramEnumeration("IOS.DefaultBrowserFullscreenPromo",
action);
break;
case DefaultPromoTypeAllTabs:
base::UmaHistogramEnumeration(
"IOS.DefaultBrowserFullscreenTailoredPromoAllTabs", action);
break;
case DefaultPromoTypeMadeForIOS:
base::UmaHistogramEnumeration(
"IOS.DefaultBrowserFullscreenTailoredPromoMadeForIOS", action);
break;
case DefaultPromoTypeStaySafe:
base::UmaHistogramEnumeration(
"IOS.DefaultBrowserFullscreenTailoredPromoStaySafe", action);
break;
default:
NOTREACHED_NORETURN();
}
}