blob: a385fedb2a6f7978e7ed8107906675ff859206ec [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/ui/tabs/organization/trigger_policies.h"
#include <cmath>
#include <numbers>
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/tabs/organization/prefs.h"
#include "components/prefs/pref_service.h"
UsageTickClock::UsageTickClock(const base::TickClock* base_clock)
: base_clock_(base_clock), start_time_(base_clock_->NowTicks()) {
if (metrics::DesktopSessionDurationTracker::IsInitialized()) {
auto* const tracker = metrics::DesktopSessionDurationTracker::Get();
tracker->AddObserver(this);
if (tracker->in_session()) {
current_usage_session_start_time_ = start_time_;
}
}
}
UsageTickClock::~UsageTickClock() {
if (metrics::DesktopSessionDurationTracker::IsInitialized()) {
metrics::DesktopSessionDurationTracker::Get()->RemoveObserver(this);
}
}
base::TimeTicks UsageTickClock::NowTicks() const {
if (!metrics::DesktopSessionDurationTracker::IsInitialized()) {
return base_clock_->NowTicks();
}
const base::TimeTicks completed_session_time =
start_time_ + usage_time_in_completed_sessions_;
if (current_usage_session_start_time_.has_value()) {
return completed_session_time + (base_clock_->NowTicks() -
current_usage_session_start_time_.value());
}
return completed_session_time;
}
void UsageTickClock::OnSessionStarted(base::TimeTicks session_start) {
DCHECK(!current_usage_session_start_time_.has_value());
// Ignore `session_start`; it doesn't come from `base_clock_`.
current_usage_session_start_time_ = base_clock_->NowTicks();
}
void UsageTickClock::OnSessionEnded(base::TimeDelta session_length,
base::TimeTicks session_end) {
DCHECK(current_usage_session_start_time_.has_value());
// Ignore `session_length`/`session_end`; they don't come from `base_clock_`.
usage_time_in_completed_sessions_ +=
base_clock_->NowTicks() - current_usage_session_start_time_.value();
current_usage_session_start_time_ = std::nullopt;
}
ProfilePrefBackoffLevelProvider::ProfilePrefBackoffLevelProvider(
content::BrowserContext* context)
: prefs_(Profile::FromBrowserContext(context)->GetPrefs()) {}
ProfilePrefBackoffLevelProvider::~ProfilePrefBackoffLevelProvider() = default;
unsigned int ProfilePrefBackoffLevelProvider::Get() const {
return prefs_->GetInteger(
tab_organization_prefs::kTabOrganizationNudgeBackoffCount);
}
void ProfilePrefBackoffLevelProvider::Increment() {
prefs_->SetInteger(tab_organization_prefs::kTabOrganizationNudgeBackoffCount,
Get() + 1);
}
void ProfilePrefBackoffLevelProvider::Decrement() {
prefs_->SetInteger(tab_organization_prefs::kTabOrganizationNudgeBackoffCount,
std::max(1u, Get()) - 1);
}
TargetFrequencyTriggerPolicy::TargetFrequencyTriggerPolicy(
std::unique_ptr<base::TickClock> clock,
base::TimeDelta base_period,
float backoff_base,
BackoffLevelProvider* backoff_level_provider)
: clock_(std::move(clock)),
base_period_(base_period),
backoff_base_(backoff_base),
backoff_level_provider_(backoff_level_provider),
cycle_start_time_(clock_->NowTicks()) {}
TargetFrequencyTriggerPolicy::~TargetFrequencyTriggerPolicy() = default;
bool TargetFrequencyTriggerPolicy::ShouldTrigger(float score) {
const base::TimeTicks current_time = clock_->NowTicks();
const base::TimeDelta period =
base_period_ * std::pow(backoff_base_, backoff_level_provider_->Get());
// Restart the cycle if `period_` has elapsed.
if (current_time > cycle_start_time_ + period) {
cycle_start_time_ += period;
best_score = std::nullopt;
base::UmaHistogramBoolean("Tab.Organization.Trigger.TriggeredInPeriod",
has_triggered_);
has_triggered_ = false;
}
// Update the best score if we're in the observation phase.
const base::TimeDelta observation_period = period / std::numbers::e_v<float>;
if (current_time < cycle_start_time_ + observation_period) {
best_score =
best_score.has_value() ? std::max(best_score.value(), score) : score;
return false;
}
// Trigger if we haven't triggered yet and have a new high score.
if (!has_triggered_ && best_score.has_value() && score > best_score) {
best_score = std::nullopt;
has_triggered_ = true;
return true;
}
return false;
}
void TargetFrequencyTriggerPolicy::OnTriggerSucceeded() {
backoff_level_provider_->Decrement();
}
void TargetFrequencyTriggerPolicy::OnTriggerFailed() {
backoff_level_provider_->Increment();
}
bool NeverTriggerPolicy::ShouldTrigger(float score) {
return false;
}
bool DemoTriggerPolicy::ShouldTrigger(float score) {
return true;
}