| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/ntp_snippets/remote/remote_suggestions_scheduler_impl.h" |
| |
| #include <random> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_split.h" |
| #include "base/time/clock.h" |
| #include "components/ntp_snippets/features.h" |
| #include "components/ntp_snippets/pref_names.h" |
| #include "components/ntp_snippets/remote/persistent_scheduler.h" |
| #include "components/ntp_snippets/remote/remote_suggestions_provider.h" |
| #include "components/ntp_snippets/status.h" |
| #include "components/ntp_snippets/user_classifier.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "net/base/network_change_notifier.h" |
| |
| namespace ntp_snippets { |
| |
| namespace { |
| |
| // The FetchingInterval enum specifies overlapping time intervals that are used |
| // for scheduling the next remote suggestion fetch. Therefore a timer is created |
| // for each interval. Initially all the timers are started at the same time. |
| // Fetches are only performed when conditions associated with the intervals are |
| // met: |
| // - A "soft" fetch is performed only at a moment when the user actively uses |
| // Chrome after the interval has elapsed and causes a trigger that is currently |
| // enabled while |
| // - a "persistent" fetch is initiated automatically by the OS after the |
| // interval elapses. |
| // - A "wifi" fetch is performed only when the user is on a connection that |
| // the OS classifies as unmetered while |
| // - a "fallback" fetch happens on any connection. |
| // If a fetch failed, then only the corresponding timer is reset. The other |
| // timers are not touched. |
| enum class FetchingInterval { |
| PERSISTENT_FALLBACK, |
| PERSISTENT_WIFI, |
| SOFT_FALLBACK, |
| SOFT_WIFI, |
| COUNT |
| }; |
| |
| // The following arrays specify default values for remote suggestions fetch |
| // intervals corresponding to individual user classes. The user classes are |
| // defined by the user classifier. There must be an array for each user class. |
| // The values of each array specify a default time interval for the intervals |
| // defined by the enum FetchingInterval. The default time intervals defined in |
| // the arrays can be overridden using different variation parameters. |
| const double kDefaultFetchingIntervalHoursRareNtpUser[] = {48.0, 24.0, 8.0, |
| 4.0}; |
| const double kDefaultFetchingIntervalHoursActiveNtpUser[] = {24.0, 8.0, 6.0, |
| 3.0}; |
| const double kDefaultFetchingIntervalHoursActiveSuggestionsConsumer[] = { |
| 24.0, 6.0, 2.0, 1.0}; |
| |
| // Variation parameters than can be used to override the default fetching |
| // intervals. |
| const char* kFetchingIntervalParamNameRareNtpUser[] = { |
| "fetching_interval_hours-fallback-rare_ntp_user", |
| "fetching_interval_hours-wifi-rare_ntp_user", |
| "soft_fetching_interval_hours-fallback-rare_ntp_user", |
| "soft_fetching_interval_hours-wifi-rare_ntp_user"}; |
| const char* kFetchingIntervalParamNameActiveNtpUser[] = { |
| "fetching_interval_hours-fallback-active_ntp_user", |
| "fetching_interval_hours-wifi-active_ntp_user", |
| "soft_fetching_interval_hours-fallback-active_ntp_user", |
| "soft_fetching_interval_hours-wifi-active_ntp_user"}; |
| const char* kFetchingIntervalParamNameActiveSuggestionsConsumer[] = { |
| "fetching_interval_hours-fallback-active_suggestions_consumer", |
| "fetching_interval_hours-wifi-active_suggestions_consumer", |
| "soft_fetching_interval_hours-fallback-active_suggestions_consumer", |
| "soft_fetching_interval_hours-wifi-active_suggestions_consumer"}; |
| |
| static_assert( |
| static_cast<unsigned int>(FetchingInterval::COUNT) == |
| arraysize(kDefaultFetchingIntervalHoursRareNtpUser) && |
| static_cast<unsigned int>(FetchingInterval::COUNT) == |
| arraysize(kDefaultFetchingIntervalHoursActiveNtpUser) && |
| static_cast<unsigned int>(FetchingInterval::COUNT) == |
| arraysize(kDefaultFetchingIntervalHoursActiveSuggestionsConsumer) && |
| static_cast<unsigned int>(FetchingInterval::COUNT) == |
| arraysize(kFetchingIntervalParamNameRareNtpUser) && |
| static_cast<unsigned int>(FetchingInterval::COUNT) == |
| arraysize(kFetchingIntervalParamNameActiveNtpUser) && |
| static_cast<unsigned int>(FetchingInterval::COUNT) == |
| arraysize(kFetchingIntervalParamNameActiveSuggestionsConsumer), |
| "Fill in all the info for fetching intervals."); |
| |
| const char* kTriggerTypeNames[] = {"persistent_scheduler_wake_up", "ntp_opened", |
| "browser_foregrounded", |
| "browser_cold_start"}; |
| |
| const char* kTriggerTypesParamName = "scheduler_trigger_types"; |
| const char* kTriggerTypesParamValueForEmptyList = "-"; |
| |
| const int kBlockBackgroundFetchesMinutesAfterClearingHistory = 30; |
| |
| const char kSnippetSoftFetchingIntervalOnUsageEventDeprecated[] = |
| "ntp_snippets.soft_fetching_interval_on_usage_event"; |
| const char kSnippetSoftFetchingIntervalOnNtpOpenedDeprecated[] = |
| "ntp_snippets.soft_fetching_interval_on_ntp_opened"; |
| |
| // Returns the time interval to use for scheduling remote suggestion fetches for |
| // the given interval and user_class. |
| base::TimeDelta GetDesiredFetchingInterval( |
| FetchingInterval interval, |
| UserClassifier::UserClass user_class) { |
| DCHECK(interval != FetchingInterval::COUNT); |
| const unsigned int index = static_cast<unsigned int>(interval); |
| DCHECK(index < arraysize(kDefaultFetchingIntervalHoursRareNtpUser)); |
| |
| double default_value_hours = 0.0; |
| const char* param_name = nullptr; |
| switch (user_class) { |
| case UserClassifier::UserClass::RARE_NTP_USER: |
| default_value_hours = kDefaultFetchingIntervalHoursRareNtpUser[index]; |
| param_name = kFetchingIntervalParamNameRareNtpUser[index]; |
| break; |
| case UserClassifier::UserClass::ACTIVE_NTP_USER: |
| default_value_hours = kDefaultFetchingIntervalHoursActiveNtpUser[index]; |
| param_name = kFetchingIntervalParamNameActiveNtpUser[index]; |
| break; |
| case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: |
| default_value_hours = |
| kDefaultFetchingIntervalHoursActiveSuggestionsConsumer[index]; |
| param_name = kFetchingIntervalParamNameActiveSuggestionsConsumer[index]; |
| break; |
| } |
| |
| double value_hours = base::GetFieldTrialParamByFeatureAsDouble( |
| ntp_snippets::kArticleSuggestionsFeature, param_name, |
| default_value_hours); |
| |
| return base::TimeDelta::FromSecondsD(value_hours * 3600.0); |
| } |
| |
| void ReportTimeUntilFirstSoftTrigger(UserClassifier::UserClass user_class, |
| base::TimeDelta time_until_first_trigger) { |
| switch (user_class) { |
| case UserClassifier::UserClass::RARE_NTP_USER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilFirstSoftTrigger.RareNTPUser", |
| time_until_first_trigger, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| case UserClassifier::UserClass::ACTIVE_NTP_USER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilFirstSoftTrigger." |
| "ActiveNTPUser", |
| time_until_first_trigger, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilFirstSoftTrigger." |
| "ActiveSuggestionsConsumer", |
| time_until_first_trigger, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| } |
| } |
| |
| void ReportTimeUntilSoftFetch(UserClassifier::UserClass user_class, |
| base::TimeDelta time_until_soft_fetch) { |
| switch (user_class) { |
| case UserClassifier::UserClass::RARE_NTP_USER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilSoftFetch." |
| "RareNTPUser", |
| time_until_soft_fetch, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| case UserClassifier::UserClass::ACTIVE_NTP_USER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilSoftFetch." |
| "ActiveNTPUser", |
| time_until_soft_fetch, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilSoftFetch." |
| "ActiveSuggestionsConsumer", |
| time_until_soft_fetch, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| } |
| } |
| |
| void ReportTimeUntilPersistentFetch( |
| UserClassifier::UserClass user_class, |
| base::TimeDelta time_until_persistent_fetch) { |
| switch (user_class) { |
| case UserClassifier::UserClass::RARE_NTP_USER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilPersistentFetch." |
| "RareNTPUser", |
| time_until_persistent_fetch, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| case UserClassifier::UserClass::ACTIVE_NTP_USER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilPersistentFetch." |
| "ActiveNTPUser", |
| time_until_persistent_fetch, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "NewTabPage.ContentSuggestions.TimeUntilPersistentFetch." |
| "ActiveSuggestionsConsumer", |
| time_until_persistent_fetch, base::TimeDelta::FromSeconds(1), |
| base::TimeDelta::FromDays(7), |
| /*bucket_count=*/50); |
| break; |
| } |
| } |
| |
| } // namespace |
| |
| class EulaState : public web_resource::EulaAcceptedNotifier::Observer { |
| public: |
| EulaState(PrefService* local_state_prefs, |
| RemoteSuggestionsScheduler* scheduler) |
| : eula_notifier_( |
| web_resource::EulaAcceptedNotifier::Create(local_state_prefs)), |
| scheduler_(scheduler) { |
| // EulaNotifier is not constructed on some platforms (such as desktop). |
| if (!eula_notifier_) { |
| return; |
| } |
| |
| // Register the observer. |
| eula_notifier_->Init(this); |
| } |
| |
| ~EulaState() = default; |
| |
| bool IsEulaAccepted() { |
| if (!eula_notifier_) { |
| return true; |
| } |
| return eula_notifier_->IsEulaAccepted(); |
| } |
| |
| // EulaAcceptedNotifier::Observer implementation. |
| void OnEulaAccepted() override { |
| // Emulate a persistent fetch - we really want to fetch, initially! |
| // TODO(jkrcal): Find a cleaner solution. This is somewhat hacky and can |
| // mess up with metrics. |
| scheduler_->OnPersistentSchedulerWakeUp(); |
| } |
| |
| private: |
| std::unique_ptr<web_resource::EulaAcceptedNotifier> eula_notifier_; |
| RemoteSuggestionsScheduler* scheduler_; |
| |
| DISALLOW_COPY_AND_ASSIGN(EulaState); |
| }; |
| |
| // static |
| RemoteSuggestionsSchedulerImpl::FetchingSchedule |
| RemoteSuggestionsSchedulerImpl::FetchingSchedule::Empty() { |
| return FetchingSchedule{base::TimeDelta(), base::TimeDelta(), |
| base::TimeDelta(), base::TimeDelta()}; |
| } |
| |
| bool RemoteSuggestionsSchedulerImpl::FetchingSchedule::operator==( |
| const FetchingSchedule& other) const { |
| return interval_persistent_wifi == other.interval_persistent_wifi && |
| interval_persistent_fallback == other.interval_persistent_fallback && |
| interval_soft_wifi == other.interval_soft_wifi && |
| interval_soft_fallback == other.interval_soft_fallback; |
| } |
| |
| bool RemoteSuggestionsSchedulerImpl::FetchingSchedule::operator!=( |
| const FetchingSchedule& other) const { |
| return !operator==(other); |
| } |
| |
| bool RemoteSuggestionsSchedulerImpl::FetchingSchedule::is_empty() const { |
| return interval_persistent_wifi.is_zero() && |
| interval_persistent_fallback.is_zero() && |
| interval_soft_wifi.is_zero() && interval_soft_fallback.is_zero(); |
| } |
| |
| // The TriggerType enum specifies values for the events that can trigger |
| // fetching remote suggestions. These values are written to logs. New enum |
| // values can be added, but existing enums must never be renumbered or deleted |
| // and reused. When adding new entries, also update the array |
| // |kTriggerTypeNames| above. |
| enum class RemoteSuggestionsSchedulerImpl::TriggerType { |
| PERSISTENT_SCHEDULER_WAKE_UP = 0, |
| NTP_OPENED = 1, |
| BROWSER_FOREGROUNDED = 2, |
| BROWSER_COLD_START = 3, |
| COUNT |
| }; |
| |
| RemoteSuggestionsSchedulerImpl::RemoteSuggestionsSchedulerImpl( |
| PersistentScheduler* persistent_scheduler, |
| const UserClassifier* user_classifier, |
| PrefService* profile_prefs, |
| PrefService* local_state_prefs, |
| std::unique_ptr<base::Clock> clock) |
| : persistent_scheduler_(persistent_scheduler), |
| provider_(nullptr), |
| background_fetch_in_progress_(false), |
| user_classifier_(user_classifier), |
| request_throttler_rare_ntp_user_( |
| profile_prefs, |
| RequestThrottler::RequestType:: |
| CONTENT_SUGGESTION_FETCHER_RARE_NTP_USER), |
| request_throttler_active_ntp_user_( |
| profile_prefs, |
| RequestThrottler::RequestType:: |
| CONTENT_SUGGESTION_FETCHER_ACTIVE_NTP_USER), |
| request_throttler_active_suggestions_consumer_( |
| profile_prefs, |
| RequestThrottler::RequestType:: |
| CONTENT_SUGGESTION_FETCHER_ACTIVE_SUGGESTIONS_CONSUMER), |
| time_until_first_trigger_reported_(false), |
| eula_state_(base::MakeUnique<EulaState>(local_state_prefs, this)), |
| profile_prefs_(profile_prefs), |
| clock_(std::move(clock)), |
| enabled_triggers_(GetEnabledTriggerTypes()) { |
| DCHECK(user_classifier); |
| DCHECK(profile_prefs); |
| |
| // Cleanup procedure in M59. Remove for M62. |
| profile_prefs_->ClearPref(kSnippetSoftFetchingIntervalOnUsageEventDeprecated); |
| profile_prefs_->ClearPref(kSnippetSoftFetchingIntervalOnNtpOpenedDeprecated); |
| |
| LoadLastFetchingSchedule(); |
| } |
| |
| RemoteSuggestionsSchedulerImpl::~RemoteSuggestionsSchedulerImpl() = default; |
| |
| // static |
| void RemoteSuggestionsSchedulerImpl::RegisterProfilePrefs( |
| PrefRegistrySimple* registry) { |
| registry->RegisterInt64Pref(prefs::kSnippetPersistentFetchingIntervalWifi, 0); |
| registry->RegisterInt64Pref(prefs::kSnippetPersistentFetchingIntervalFallback, |
| 0); |
| registry->RegisterInt64Pref(prefs::kSnippetSoftFetchingIntervalWifi, 0); |
| registry->RegisterInt64Pref(prefs::kSnippetSoftFetchingIntervalFallback, 0); |
| registry->RegisterInt64Pref(prefs::kSnippetLastFetchAttempt, 0); |
| |
| // Cleanup procedure in M59. Remove for M62. |
| registry->RegisterInt64Pref( |
| kSnippetSoftFetchingIntervalOnUsageEventDeprecated, 0); |
| registry->RegisterInt64Pref(kSnippetSoftFetchingIntervalOnNtpOpenedDeprecated, |
| 0); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::SetProvider( |
| RemoteSuggestionsProvider* provider) { |
| DCHECK(provider); |
| provider_ = provider; |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnProviderActivated() { |
| StartScheduling(); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnProviderDeactivated() { |
| StopScheduling(); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnSuggestionsCleared() { |
| // This should be called by |provider_| so it should exist. |
| DCHECK(provider_); |
| // Some user action causes suggestions to be cleared, fetch now (as an |
| // interactive request). |
| provider_->ReloadSuggestions(); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnHistoryCleared() { |
| // Due to privacy, we should not fetch for a while (unless the user explicitly |
| // asks for new suggestions) to give sync the time to propagate the changes in |
| // history to the server. |
| background_fetches_allowed_after_ = |
| clock_->Now() + base::TimeDelta::FromMinutes( |
| kBlockBackgroundFetchesMinutesAfterClearingHistory); |
| // After that time elapses, we should fetch as soon as possible. |
| ClearLastFetchAttemptTime(); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::RescheduleFetching() { |
| // Force the reschedule by stopping and starting it again. |
| StopScheduling(); |
| StartScheduling(); |
| } |
| |
| bool RemoteSuggestionsSchedulerImpl::AcquireQuotaForInteractiveFetch() { |
| return AcquireQuota(/*interactive_request=*/true); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnInteractiveFetchFinished( |
| Status fetch_status) { |
| OnFetchCompleted(fetch_status); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnPersistentSchedulerWakeUp() { |
| RefetchInTheBackgroundIfAppropriate( |
| TriggerType::PERSISTENT_SCHEDULER_WAKE_UP); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnBrowserForegrounded() { |
| // TODO(jkrcal): Consider that this is called whenever we open or return to an |
| // Activity. Therefore, keep work light for fast start up calls. |
| RefetchInTheBackgroundIfAppropriate(TriggerType::BROWSER_FOREGROUNDED); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnBrowserColdStart() { |
| // TODO(jkrcal): Consider that work here must be kept light for fast |
| // cold start ups. |
| RefetchInTheBackgroundIfAppropriate(TriggerType::BROWSER_COLD_START); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnNTPOpened() { |
| // TODO(jkrcal): Consider that this is called whenever we open an NTP. |
| // Therefore, keep work light for fast start up calls. |
| RefetchInTheBackgroundIfAppropriate(TriggerType::NTP_OPENED); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::StartScheduling() { |
| FetchingSchedule new_schedule = GetDesiredFetchingSchedule(); |
| |
| if (schedule_ == new_schedule) { |
| // Do not schedule if nothing has changed; |
| return; |
| } |
| |
| schedule_ = new_schedule; |
| StoreFetchingSchedule(); |
| ApplyPersistentFetchingSchedule(); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::StopScheduling() { |
| if (schedule_.is_empty()) { |
| // Do not unschedule if already switched off. |
| return; |
| } |
| |
| schedule_ = FetchingSchedule::Empty(); |
| StoreFetchingSchedule(); |
| ApplyPersistentFetchingSchedule(); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::ApplyPersistentFetchingSchedule() { |
| // The scheduler only exists on Android so far, it's null on other platforms. |
| if (persistent_scheduler_) { |
| if (schedule_.is_empty()) { |
| persistent_scheduler_->Unschedule(); |
| } else { |
| persistent_scheduler_->Schedule(schedule_.interval_persistent_wifi, |
| schedule_.interval_persistent_fallback); |
| } |
| } |
| } |
| |
| RemoteSuggestionsSchedulerImpl::FetchingSchedule |
| RemoteSuggestionsSchedulerImpl::GetDesiredFetchingSchedule() const { |
| UserClassifier::UserClass user_class = user_classifier_->GetUserClass(); |
| |
| FetchingSchedule schedule; |
| schedule.interval_persistent_wifi = |
| GetDesiredFetchingInterval(FetchingInterval::PERSISTENT_WIFI, user_class); |
| schedule.interval_persistent_fallback = GetDesiredFetchingInterval( |
| FetchingInterval::PERSISTENT_FALLBACK, user_class); |
| schedule.interval_soft_wifi = |
| GetDesiredFetchingInterval(FetchingInterval::SOFT_WIFI, user_class); |
| schedule.interval_soft_fallback = |
| GetDesiredFetchingInterval(FetchingInterval::SOFT_FALLBACK, user_class); |
| |
| return schedule; |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::LoadLastFetchingSchedule() { |
| schedule_.interval_persistent_wifi = base::TimeDelta::FromInternalValue( |
| profile_prefs_->GetInt64(prefs::kSnippetPersistentFetchingIntervalWifi)); |
| schedule_.interval_persistent_fallback = |
| base::TimeDelta::FromInternalValue(profile_prefs_->GetInt64( |
| prefs::kSnippetPersistentFetchingIntervalFallback)); |
| schedule_.interval_soft_wifi = base::TimeDelta::FromInternalValue( |
| profile_prefs_->GetInt64(prefs::kSnippetSoftFetchingIntervalWifi)); |
| schedule_.interval_soft_fallback = base::TimeDelta::FromInternalValue( |
| profile_prefs_->GetInt64(prefs::kSnippetSoftFetchingIntervalFallback)); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::StoreFetchingSchedule() { |
| profile_prefs_->SetInt64( |
| prefs::kSnippetPersistentFetchingIntervalWifi, |
| schedule_.interval_persistent_wifi.ToInternalValue()); |
| profile_prefs_->SetInt64( |
| prefs::kSnippetPersistentFetchingIntervalFallback, |
| schedule_.interval_persistent_fallback.ToInternalValue()); |
| profile_prefs_->SetInt64(prefs::kSnippetSoftFetchingIntervalWifi, |
| schedule_.interval_soft_wifi.ToInternalValue()); |
| profile_prefs_->SetInt64(prefs::kSnippetSoftFetchingIntervalFallback, |
| schedule_.interval_soft_fallback.ToInternalValue()); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::RefetchInTheBackgroundIfAppropriate( |
| TriggerType trigger) { |
| if (background_fetch_in_progress_) { |
| return; |
| } |
| |
| if (BackgroundFetchesDisabled(trigger)) { |
| return; |
| } |
| |
| bool is_soft = trigger != TriggerType::PERSISTENT_SCHEDULER_WAKE_UP; |
| const base::Time last_fetch_attempt_time = base::Time::FromInternalValue( |
| profile_prefs_->GetInt64(prefs::kSnippetLastFetchAttempt)); |
| |
| if (is_soft && !time_until_first_trigger_reported_) { |
| time_until_first_trigger_reported_ = true; |
| ReportTimeUntilFirstSoftTrigger(user_classifier_->GetUserClass(), |
| clock_->Now() - last_fetch_attempt_time); |
| } |
| |
| if (is_soft && !ShouldRefetchInTheBackgroundNow(last_fetch_attempt_time)) { |
| return; |
| } |
| |
| if (!AcquireQuota(/*interactive_request=*/false)) { |
| return; |
| } |
| |
| if (is_soft) { |
| ReportTimeUntilSoftFetch(user_classifier_->GetUserClass(), |
| clock_->Now() - last_fetch_attempt_time); |
| } else { |
| ReportTimeUntilPersistentFetch(user_classifier_->GetUserClass(), |
| clock_->Now() - last_fetch_attempt_time); |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "NewTabPage.ContentSuggestions.BackgroundFetchTrigger", |
| static_cast<int>(trigger), static_cast<int>(TriggerType::COUNT)); |
| |
| background_fetch_in_progress_ = true; |
| provider_->RefetchInTheBackground(base::Bind( |
| &RemoteSuggestionsSchedulerImpl::RefetchInTheBackgroundFinished, |
| base::Unretained(this))); |
| } |
| |
| bool RemoteSuggestionsSchedulerImpl::ShouldRefetchInTheBackgroundNow( |
| base::Time last_fetch_attempt_time) { |
| // If we have no persistent scheduler to ask, err on the side of caution. |
| bool wifi = false; |
| if (persistent_scheduler_) { |
| wifi = persistent_scheduler_->IsOnUnmeteredConnection(); |
| } |
| |
| base::Time first_allowed_fetch_time = |
| last_fetch_attempt_time + |
| (wifi ? schedule_.interval_soft_wifi : schedule_.interval_soft_fallback); |
| base::Time now = clock_->Now(); |
| return background_fetches_allowed_after_ <= now && |
| first_allowed_fetch_time <= now; |
| } |
| |
| bool RemoteSuggestionsSchedulerImpl::BackgroundFetchesDisabled( |
| TriggerType trigger) const { |
| if (!provider_) { |
| return true; // Cannot fetch as remote suggestions provider does not exist. |
| } |
| |
| if (schedule_.is_empty()) { |
| return true; // Background fetches are disabled in general. |
| } |
| |
| if (enabled_triggers_.count(trigger) == 0) { |
| return true; // Background fetches for |trigger| are not enabled. |
| } |
| |
| if (!eula_state_->IsEulaAccepted()) { |
| return true; // No background fetches are allowed before EULA is accepted. |
| } |
| |
| return false; |
| } |
| |
| bool RemoteSuggestionsSchedulerImpl::AcquireQuota(bool interactive_request) { |
| switch (user_classifier_->GetUserClass()) { |
| case UserClassifier::UserClass::RARE_NTP_USER: |
| return request_throttler_rare_ntp_user_.DemandQuotaForRequest( |
| interactive_request); |
| case UserClassifier::UserClass::ACTIVE_NTP_USER: |
| return request_throttler_active_ntp_user_.DemandQuotaForRequest( |
| interactive_request); |
| case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: |
| return request_throttler_active_suggestions_consumer_ |
| .DemandQuotaForRequest(interactive_request); |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::RefetchInTheBackgroundFinished( |
| Status fetch_status) { |
| background_fetch_in_progress_ = false; |
| OnFetchCompleted(fetch_status); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::OnFetchCompleted(Status fetch_status) { |
| profile_prefs_->SetInt64(prefs::kSnippetLastFetchAttempt, |
| clock_->Now().ToInternalValue()); |
| time_until_first_trigger_reported_ = false; |
| |
| // Reschedule after a fetch. The persistent schedule is applied only after a |
| // successful fetch. After a failed fetch, we want to keep the previous |
| // persistent schedule intact so that we eventually get a persistent |
| // fallback fetch (if the wifi persistent fetches keep failing). |
| if (fetch_status.code != StatusCode::SUCCESS) { |
| return; |
| } |
| ApplyPersistentFetchingSchedule(); |
| } |
| |
| void RemoteSuggestionsSchedulerImpl::ClearLastFetchAttemptTime() { |
| profile_prefs_->ClearPref(prefs::kSnippetLastFetchAttempt); |
| } |
| |
| std::set<RemoteSuggestionsSchedulerImpl::TriggerType> |
| RemoteSuggestionsSchedulerImpl::GetEnabledTriggerTypes() { |
| static_assert(static_cast<unsigned int>(TriggerType::COUNT) == |
| arraysize(kTriggerTypeNames), |
| "Fill in names for trigger types."); |
| |
| std::string param_value = base::GetFieldTrialParamValueByFeature( |
| ntp_snippets::kArticleSuggestionsFeature, kTriggerTypesParamName); |
| if (param_value == kTriggerTypesParamValueForEmptyList) { |
| return std::set<TriggerType>(); |
| } |
| |
| std::vector<std::string> tokens = base::SplitString( |
| param_value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| if (tokens.empty()) { |
| return GetDefaultEnabledTriggerTypes(); |
| } |
| |
| std::set<TriggerType> enabled_types; |
| for (const auto& token : tokens) { |
| auto** it = std::find(std::begin(kTriggerTypeNames), |
| std::end(kTriggerTypeNames), token); |
| if (it == std::end(kTriggerTypeNames)) { |
| DLOG(WARNING) << "Failed to parse variation param " |
| << kTriggerTypesParamName << " with string value " |
| << param_value |
| << " into a comma-separated list of keywords. " |
| << "Unknown token " << token |
| << " found. Falling back to default value."; |
| return GetDefaultEnabledTriggerTypes(); |
| } |
| |
| // Add the enabled type represented by |token| into the result set. |
| enabled_types.insert( |
| static_cast<TriggerType>(it - std::begin(kTriggerTypeNames))); |
| } |
| return enabled_types; |
| } |
| |
| std::set<RemoteSuggestionsSchedulerImpl::TriggerType> |
| RemoteSuggestionsSchedulerImpl::GetDefaultEnabledTriggerTypes() { |
| return {TriggerType::PERSISTENT_SCHEDULER_WAKE_UP, TriggerType::NTP_OPENED}; |
| } |
| |
| } // namespace ntp_snippets |