blob: dfb0193bc815b130d41b7e1cd5d206a6520ba9b7 [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.
#ifndef CHROME_BROWSER_UI_TABS_ORGANIZATION_TRIGGER_POLICIES_H_
#define CHROME_BROWSER_UI_TABS_ORGANIZATION_TRIGGER_POLICIES_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
#include "chrome/browser/ui/tabs/organization/trigger.h"
namespace content {
class BrowserContext;
}
class PrefService;
// We want to parameterize trigger policies with things like target triggering
// frequencies. That begs the question - how should we define those frequencies?
// Once per week? Once every 8 hours of active chrome usage? Once per 200 tabs
// opened?
// A simple way to model this is with clocks that measure time differently. Some
// clock options, in increasing complexity order:
// 1. Wall time. Simple and predictable, but could spam users who don't often
// use Chrome and under-serve users who use Chrome frequently.
// 2. Chrome foreground time, implemented below. Still fairly simple, and maps
// better to actual usage, but fails for cases like streaming a movie or leaving
// a computer on overnight.
// 3. Number of browser actions of some kind, e.g. page loads. Weirder to think
// about, some risk of degenerate behavior for unusual usage patterns, but can
// map very directly to e.g. tabstrip usage.
// 4. The above, but deduplicate events performed in quick succession. This
// effectively amounts to defining our own notion of 'active tabstrip time'.
// Fixes the issues with 3 but might just be overkill.
// A clock that runs only while Chrome is in the foreground. See also
// chrome/browser/resource_coordinator/usage_clock.h which implements the same
// concept in a resource_coordinator specific way.
class UsageTickClock final : public base::TickClock,
metrics::DesktopSessionDurationTracker::Observer {
public:
explicit UsageTickClock(const base::TickClock* base_clock);
UsageTickClock(const UsageTickClock&) = delete;
UsageTickClock& operator=(const UsageTickClock&) = delete;
~UsageTickClock() override;
base::TimeTicks NowTicks() const override;
private:
void OnSessionStarted(base::TimeTicks session_start) override;
void OnSessionEnded(base::TimeDelta session_length,
base::TimeTicks session_end) override;
const raw_ptr<const base::TickClock> base_clock_;
const base::TimeTicks start_time_;
base::TimeDelta usage_time_in_completed_sessions_ = base::TimeDelta();
std::optional<base::TimeTicks> current_usage_session_start_time_ =
std::nullopt;
};
class BackoffLevelProvider {
public:
virtual ~BackoffLevelProvider() = default;
virtual unsigned int Get() const = 0;
virtual void Increment() = 0;
virtual void Decrement() = 0;
};
class ProfilePrefBackoffLevelProvider final : public BackoffLevelProvider {
public:
explicit ProfilePrefBackoffLevelProvider(content::BrowserContext* context);
~ProfilePrefBackoffLevelProvider() override;
// BackoffLevelProvider:
unsigned int Get() const override;
void Increment() override;
void Decrement() override;
private:
raw_ptr<PrefService> prefs_;
};
// A policy which triggers up to once per period, based on the classic solution
// to the secretary problem. Has an observation phase and a trigger phase.
// During the observation phase, it keeps track of the best score seen so far.
// During the trigger phase, it triggers the first time the best score from the
// observation phase is beaten.
//
// For any given period, it has a 1/e chance to not trigger at all, but the rest
// of the time it will likely trigger on a very good moment, relative to the
// other moments in this period.
class TargetFrequencyTriggerPolicy final : public TriggerPolicy {
public:
TargetFrequencyTriggerPolicy(std::unique_ptr<base::TickClock> clock,
base::TimeDelta base_period,
float backoff_base,
BackoffLevelProvider* backoff_level_provider);
~TargetFrequencyTriggerPolicy() override;
bool ShouldTrigger(float score) override;
void OnTriggerSucceeded();
void OnTriggerFailed();
private:
const std::unique_ptr<base::TickClock> clock_;
const base::TimeDelta base_period_;
const float backoff_base_;
const raw_ptr<BackoffLevelProvider> backoff_level_provider_;
base::TimeTicks cycle_start_time_;
std::optional<float> best_score = std::nullopt;
bool has_triggered_ = false;
};
// Never trigger. Useful for disabling the trigger under certain conditions.
class NeverTriggerPolicy final : public TriggerPolicy {
public:
bool ShouldTrigger(float score) override;
};
// Trigger every time. Very spammy, but suitable for testing or demoing.
class DemoTriggerPolicy final : public TriggerPolicy {
public:
bool ShouldTrigger(float score) override;
};
#endif // CHROME_BROWSER_UI_TABS_ORGANIZATION_TRIGGER_POLICIES_H_