Skip to content

Commit

Permalink
Prevent site engagement scores from decaying when Chrome isn't in use.
Browse files Browse the repository at this point in the history
The site engagement service decays origins which have not been accessed
in a week. However, if a user isn't using Chrome at all, the decay still
occurs.

This CL addresses this issue by persisting the last recorded time that
any engagement occurred. If too long a period passes (currently 10 days)
without *any* engagement, all scores have their last accessed time
adjusted to be relative to some number of decay periods prior to now
(currently set at 1 decay period), based on their offset to the last known
engagement time.

BUG=575504

Review-Url: https://codereview.chromium.org/2082953002
Cr-Commit-Position: refs/heads/master@{#402653}
  • Loading branch information
dominickn authored and Commit bot committed Jun 29, 2016
1 parent e6ea446 commit 709246a
Show file tree
Hide file tree
Showing 8 changed files with 436 additions and 70 deletions.
14 changes: 14 additions & 0 deletions chrome/browser/engagement/site_engagement_score.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const char* kVariationNames[] = {
"first_daily_engagement_points",
"medium_engagement_boundary",
"high_engagement_boundary",
"max_decays_per_score",
"last_engagement_grace_period_in_hours",
};

bool DoublesConsideredDifferent(double value1, double value2, double delta) {
Expand Down Expand Up @@ -83,6 +85,8 @@ double SiteEngagementScore::param_values[] = {
8, // BOOTSTRAP_POINTS
5, // MEDIUM_ENGAGEMENT_BOUNDARY
50, // HIGH_ENGAGEMENT_BOUNDARY
1, // MAX_DECAYS_PER_SCORE
72, // LAST_ENGAGEMENT_GRACE_PERIOD_IN_HOURS
};

const char* SiteEngagementScore::kRawScoreKey = "rawScore";
Expand Down Expand Up @@ -139,6 +143,14 @@ double SiteEngagementScore::GetHighEngagementBoundary() {
return param_values[HIGH_ENGAGEMENT_BOUNDARY];
}

double SiteEngagementScore::GetMaxDecaysPerScore() {
return param_values[MAX_DECAYS_PER_SCORE];
}

double SiteEngagementScore::GetLastEngagementGracePeriodInHours() {
return param_values[LAST_ENGAGEMENT_GRACE_PERIOD_IN_HOURS];
}

// static
void SiteEngagementScore::UpdateFromVariations(const char* param_name) {
double param_vals[MAX_VARIATION];
Expand Down Expand Up @@ -337,6 +349,8 @@ void SiteEngagementScore::SetParamValuesForTesting() {
param_values[BOOTSTRAP_POINTS] = 8;
param_values[MEDIUM_ENGAGEMENT_BOUNDARY] = 5;
param_values[HIGH_ENGAGEMENT_BOUNDARY] = 50;
param_values[MAX_DECAYS_PER_SCORE] = 1;
param_values[LAST_ENGAGEMENT_GRACE_PERIOD_IN_HOURS] = 72;

// This is set to zero to avoid interference with tests and is set when
// testing this functionality.
Expand Down
27 changes: 23 additions & 4 deletions chrome/browser/engagement/site_engagement_score.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ class SiteEngagementScore {
MEDIUM_ENGAGEMENT_BOUNDARY,
HIGH_ENGAGEMENT_BOUNDARY,

// The maximum number of decays that a SiteEngagementScore can incur before
// entering a grace period. MAX_DECAYS_PER_SCORE * DECAY_PERIOD_IN_DAYS is
// the max decay period, i.e. the maximum duration permitted for
// (clock_->Now() - score.last_engagement_time()).
MAX_DECAYS_PER_SCORE,

// If a SiteEngagamentScore has not been accessed or updated for a period
// longer than the max decay period + LAST_ENGAGEMENT_GRACE_PERIOD_IN_HOURS
// (see above), its last engagement time will be reset to be max decay
// period prior to clock_->Now().
LAST_ENGAGEMENT_GRACE_PERIOD_IN_HOURS,

MAX_VARIATION
};

Expand All @@ -80,6 +92,8 @@ class SiteEngagementScore {
static double GetBootstrapPoints();
static double GetMediumEngagementBoundary();
static double GetHighEngagementBoundary();
static double GetMaxDecaysPerScore();
static double GetLastEngagementGracePeriodInHours();

// Update the default engagement settings via variations.
static void UpdateFromVariations(const char* param_name);
Expand Down Expand Up @@ -108,10 +122,7 @@ class SiteEngagementScore {
bool MaxPointsPerDayAdded() const;

// Resets the score to |points| and resets the daily point limit. If
// |updated_time| is non-null, sets the last engagement time and last
// shortcut launch time (if it is non-null) to |updated_time|. Otherwise, last
// engagement time is set to the current time and last shortcut launch time is
// left unchanged.
// |updated_time| is non-null, sets the last engagement time to that value.
void Reset(double points, const base::Time updated_time);

// Get/set the last time this origin was launched from an installed shortcut.
Expand All @@ -122,6 +133,14 @@ class SiteEngagementScore {
last_shortcut_launch_time_ = time;
}

// Get/set the last time this origin recorded an engagement change.
base::Time last_engagement_time() const {
return last_engagement_time_;
}
void set_last_engagement_time(const base::Time& time) {
last_engagement_time_ = time;
}

private:
FRIEND_TEST_ALL_PREFIXES(SiteEngagementScoreTest, PartiallyEmptyDictionary);
FRIEND_TEST_ALL_PREFIXES(SiteEngagementScoreTest, PopulatedDictionary);
Expand Down
111 changes: 98 additions & 13 deletions chrome/browser/engagement/site_engagement_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@
#include "chrome/browser/engagement/site_engagement_score.h"
#include "chrome/browser/engagement/site_engagement_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/history/core/browser/history_service.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "url/gurl.h"
Expand Down Expand Up @@ -221,6 +224,11 @@ void SiteEngagementService::SetLastShortcutLaunchTime(const GURL& url) {
}

double SiteEngagementService::GetScore(const GURL& url) const {
// Ensure that if engagement is stale, we clean things up before fetching the
// score.
if (IsLastEngagementStale())
CleanupEngagementScores(true);

return CreateEngagementScore(url).GetScore();
}

Expand All @@ -245,32 +253,81 @@ SiteEngagementService::SiteEngagementService(Profile* profile,
}

void SiteEngagementService::AddPoints(const GURL& url, double points) {
SiteEngagementScore score = CreateEngagementScore(url);
// Trigger a cleanup and date adjustment if it has been a substantial length
// of time since *any* engagement was recorded by the service. This will
// ensure that we do not decay scores when the user did not use the browser.
if (IsLastEngagementStale())
CleanupEngagementScores(true);

SiteEngagementScore score = CreateEngagementScore(url);
score.AddPoints(points);
score.Commit();

SetLastEngagementTime(score.last_engagement_time());
}

void SiteEngagementService::AfterStartupTask() {
CleanupEngagementScores();
// Check if we need to reset last engagement times on startup - we want to
// avoid doing this in AddPoints() if possible. It is still necessary to check
// in AddPoints for people who never restart Chrome, but leave it open and
// their computer on standby.
CleanupEngagementScores(IsLastEngagementStale());
RecordMetrics();
}

void SiteEngagementService::CleanupEngagementScores() {
void SiteEngagementService::CleanupEngagementScores(
bool update_last_engagement_time) const {
// This method should not be called with |update_last_engagement_time| = true
// if the last engagement time isn't stale.
DCHECK(!update_last_engagement_time || IsLastEngagementStale());

HostContentSettingsMap* settings_map =
HostContentSettingsMapFactory::GetForProfile(profile_);
std::unique_ptr<ContentSettingsForOneType> engagement_settings =
GetEngagementContentSettings(settings_map);

// We want to rebase last engagement times relative to MaxDecaysPerScore
// periods of decay in the past.
base::Time now = clock_->Now();
base::Time last_engagement_time = GetLastEngagementTime();
base::Time rebase_time = now - GetMaxDecayPeriod();
base::Time new_last_engagement_time;
for (const auto& site : *engagement_settings) {
GURL origin(site.primary_pattern.ToString());
if (origin.is_valid() && GetScore(origin) != 0)
continue;

if (origin.is_valid()) {
SiteEngagementScore score = CreateEngagementScore(origin);
if (update_last_engagement_time) {
// Work out the offset between this score's last engagement time and the
// last time the service recorded any engagement. Set the score's last
// engagement time to rebase_time - offset to preserve its state,
// relative to the rebase date. This ensures that the score will decay
// the next time it is used, but will not decay too much.
DCHECK_LE(score.last_engagement_time(), rebase_time);
base::TimeDelta offset =
last_engagement_time - score.last_engagement_time();
base::Time rebase_score_time = rebase_time - offset;
score.set_last_engagement_time(rebase_score_time);
if (rebase_score_time > new_last_engagement_time)
new_last_engagement_time = rebase_score_time;

score.Commit();
}

if (score.GetScore() != 0)
continue;
}

// This origin has a score of 0. Wipe it from content settings.
settings_map->SetWebsiteSettingDefaultScope(
origin, GURL(), CONTENT_SETTINGS_TYPE_SITE_ENGAGEMENT, std::string(),
nullptr);
}

// Set the last engagement time to be consistent with the scores. This will
// only occur if |update_last_engagement_time| is true.
if (!new_last_engagement_time.is_null())
SetLastEngagementTime(new_last_engagement_time);
}

void SiteEngagementService::RecordMetrics() {
Expand Down Expand Up @@ -307,6 +364,29 @@ void SiteEngagementService::RecordMetrics() {
}
}

base::Time SiteEngagementService::GetLastEngagementTime() const {
return base::Time::FromInternalValue(
profile_->GetPrefs()->GetInt64(prefs::kSiteEngagementLastUpdateTime));
}

void SiteEngagementService::SetLastEngagementTime(
base::Time last_engagement_time) const {
profile_->GetPrefs()->SetInt64(prefs::kSiteEngagementLastUpdateTime,
last_engagement_time.ToInternalValue());
}

base::TimeDelta SiteEngagementService::GetMaxDecayPeriod() const {
return base::TimeDelta::FromDays(
SiteEngagementScore::GetDecayPeriodInDays()) *
SiteEngagementScore::GetMaxDecaysPerScore();
}

base::TimeDelta SiteEngagementService::GetStalePeriod() const {
return GetMaxDecayPeriod() +
base::TimeDelta::FromHours(
SiteEngagementScore::GetLastEngagementGracePeriodInHours());
}

double SiteEngagementService::GetMedianEngagement(
const std::map<GURL, double>& score_map) const {
if (score_map.size() == 0)
Expand Down Expand Up @@ -372,6 +452,16 @@ void SiteEngagementService::HandleUserInput(
OnEngagementIncreased(web_contents, url, GetScore(url)));
}

bool SiteEngagementService::IsLastEngagementStale() const {
// This only happens when Chrome is first run and the user has never recorded
// any engagement.
base::Time last_engagement_time = GetLastEngagementTime();
if (last_engagement_time.is_null())
return false;

return (clock_->Now() - last_engagement_time) >= GetStalePeriod();
}

void SiteEngagementService::OnURLsDeleted(
history::HistoryService* history_service,
bool all_history,
Expand All @@ -391,15 +481,8 @@ void SiteEngagementService::OnURLsDeleted(
weak_factory_.GetWeakPtr(), hs, origins, expired));
}

const SiteEngagementScore SiteEngagementService::CreateEngagementScore(
const GURL& origin) const {
return SiteEngagementScore(
clock_.get(), origin,
HostContentSettingsMapFactory::GetForProfile(profile_));
}

SiteEngagementScore SiteEngagementService::CreateEngagementScore(
const GURL& origin) {
const GURL& origin) const {
return SiteEngagementScore(
clock_.get(), origin,
HostContentSettingsMapFactory::GetForProfile(profile_));
Expand Down Expand Up @@ -503,4 +586,6 @@ void SiteEngagementService::GetCountsAndLastVisitForOriginsComplete(

engagement_score.Commit();
}

SetLastEngagementTime(now);
}
36 changes: 31 additions & 5 deletions chrome/browser/engagement/site_engagement_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class SiteEngagementService : public KeyedService,
FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest, EngagementLevel);
FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest, Observers);
FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest, ScoreDecayHistograms);
FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest, LastEngagementTime);
FRIEND_TEST_ALL_PREFIXES(AppBannerSettingsHelperTest, SiteEngagementTrigger);

// Only used in tests.
Expand All @@ -140,15 +141,34 @@ class SiteEngagementService : public KeyedService,
void AddPoints(const GURL& url, double points);

// Retrieves the SiteEngagementScore object for |origin|.
SiteEngagementScore CreateEngagementScore(const GURL& origin);
const SiteEngagementScore CreateEngagementScore(const GURL& origin) const;
SiteEngagementScore CreateEngagementScore(const GURL& origin) const;

// Post startup tasks: cleaning up origins which have decayed to 0, and
// logging UMA statistics.
// Runs site engagement maintenance tasks.
void AfterStartupTask();
void CleanupEngagementScores();

// Removes any origins which have decayed to 0 engagement. If
// |update_last_engagement_time| is true, the last engagement time of all
// origins is reset by calculating the delta between the last engagement event
// recorded by the site engagement service and the origin. The origin's last
// engagement time is then set to clock_->Now() - delta.
//
// If a user does not use the browser at all for some period of time,
// engagement is not decayed, and the state is restored equivalent to how they
// left it once they return.
void CleanupEngagementScores(bool update_last_engagement_time) const;

// Records UMA metrics.
void RecordMetrics();

// Get and set the last engagement time from prefs.
base::Time GetLastEngagementTime() const;
void SetLastEngagementTime(base::Time last_engagement_time) const;

// Get the maximum decay period and the stale period for last engagement
// times.
base::TimeDelta GetMaxDecayPeriod() const;
base::TimeDelta GetStalePeriod() const;

// Returns the median engagement score of all recorded origins.
double GetMedianEngagement(const std::map<GURL, double>& score_map) const;

Expand All @@ -167,6 +187,12 @@ class SiteEngagementService : public KeyedService,
void HandleUserInput(content::WebContents* web_contents,
SiteEngagementMetrics::EngagementType type);

// Returns true if the last engagement increasing event seen by the site
// engagement service was sufficiently long ago that we need to reset all
// scores to be relative to now. This ensures that users who do not use the
// browser for an extended period of time do not have their engagement decay.
bool IsLastEngagementStale() const;

// Overridden from history::HistoryServiceObserver:
void OnURLsDeleted(history::HistoryService* history_service,
bool all_history,
Expand Down
Loading

0 comments on commit 709246a

Please sign in to comment.