1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef CHROME_BROWSER_ENGAGEMENT_SITE_ENGAGEMENT_SCORE_H_
6 #define CHROME_BROWSER_ENGAGEMENT_SITE_ENGAGEMENT_SCORE_H_
7 
8 #include <array>
9 #include <memory>
10 #include <string>
11 #include <utility>
12 
13 #include "base/gtest_prod_util.h"
14 #include "base/macros.h"
15 #include "base/time/time.h"
16 #include "base/values.h"
17 #include "components/site_engagement/core/mojom/site_engagement_details.mojom-forward.h"
18 #include "third_party/blink/public/mojom/site_engagement/site_engagement.mojom-forward.h"
19 #include "url/gurl.h"
20 
21 namespace base {
22 class Clock;
23 }
24 
25 class HostContentSettingsMap;
26 
27 class SiteEngagementScore {
28  public:
29   // The parameters which can be varied via field trial.
30   enum Variation {
31     // The maximum number of points that can be accrued in one day.
32     MAX_POINTS_PER_DAY = 0,
33 
34     // The period over which site engagement decays.
35     DECAY_PERIOD_IN_HOURS,
36 
37     // The number of points to decay per period.
38     DECAY_POINTS,
39 
40     // The proportion [0-1] which the current engagement value is multiplied by
41     // at each decay period, before subtracting DECAY_POINTS.
42     DECAY_PROPORTION,
43 
44     // A score will be erased from the engagement system if it's less than this
45     // value.
46     SCORE_CLEANUP_THRESHOLD,
47 
48     // The number of points given for navigations.
49     NAVIGATION_POINTS,
50 
51     // The number of points given for user input.
52     USER_INPUT_POINTS,
53 
54     // The number of points given for media playing. Initially calibrated such
55     // that at least 30 minutes of foreground media would be required to allow a
56     // site to reach the daily engagement maximum.
57     VISIBLE_MEDIA_POINTS,
58     HIDDEN_MEDIA_POINTS,
59 
60     // The number of points added to engagement when a site is launched from
61     // homescreen or added as a bookmark app. This bonus will apply for ten days
62     // following a launch; each new launch resets the ten days.
63     WEB_APP_INSTALLED_POINTS,
64 
65     // The number of points given for the first engagement event of the day for
66     // each site.
67     FIRST_DAILY_ENGAGEMENT,
68 
69     // The number of points that the engagement service must accumulate to be
70     // considered 'useful'.
71     BOOTSTRAP_POINTS,
72 
73     // The boundaries between low/medium and medium/high engagement as returned
74     // by GetEngagementLevel().
75     MEDIUM_ENGAGEMENT_BOUNDARY,
76     HIGH_ENGAGEMENT_BOUNDARY,
77 
78     // The maximum number of decays that a SiteEngagementScore can incur before
79     // entering a grace period. MAX_DECAYS_PER_SCORE * DECAY_PERIOD_IN_DAYS is
80     // the max decay period, i.e. the maximum duration permitted for
81     // (clock_->Now() - score.last_engagement_time()).
82     MAX_DECAYS_PER_SCORE,
83 
84     // If a SiteEngagamentScore has not been accessed or updated for a period
85     // longer than the max decay period + LAST_ENGAGEMENT_GRACE_PERIOD_IN_HOURS
86     // (see above), its last engagement time will be reset to be max decay
87     // period prior to clock_->Now().
88     LAST_ENGAGEMENT_GRACE_PERIOD_IN_HOURS,
89 
90     // The number of points given for interacting with a displayed notification.
91     NOTIFICATION_INTERACTION_POINTS,
92 
93     MAX_VARIATION
94   };
95 
96   // The maximum number of points that are allowed.
97   static const double kMaxPoints;
98 
99   static double GetMaxPointsPerDay();
100   static double GetDecayPeriodInHours();
101   static double GetDecayPoints();
102   static double GetDecayProportion();
103   static double GetScoreCleanupThreshold();
104   static double GetNavigationPoints();
105   static double GetUserInputPoints();
106   static double GetVisibleMediaPoints();
107   static double GetHiddenMediaPoints();
108   static double GetWebAppInstalledPoints();
109   static double GetFirstDailyEngagementPoints();
110   static double GetBootstrapPoints();
111   static double GetMediumEngagementBoundary();
112   static double GetHighEngagementBoundary();
113   static double GetMaxDecaysPerScore();
114   static double GetLastEngagementGracePeriodInHours();
115   static double GetNotificationInteractionPoints();
116 
117   // Sets fixed parameter values for testing site engagement. Ensure that any
118   // newly added parameters receive a fixed value here.
119   static void SetParamValuesForTesting();
120 
121   // Update the default engagement settings via variations.
122   static void UpdateFromVariations(const char* param_name);
123 
124   // The SiteEngagementScore does not take ownership of |clock|. It is the
125   // responsibility of the caller to make sure |clock| outlives this
126   // SiteEngagementScore.
127   SiteEngagementScore(base::Clock* clock,
128                       const GURL& origin,
129                       HostContentSettingsMap* settings);
130   SiteEngagementScore(SiteEngagementScore&& other);
131   ~SiteEngagementScore();
132 
133   SiteEngagementScore& operator=(SiteEngagementScore&& other);
134 
135   // Adds |points| to this score, respecting daily limits and the maximum
136   // possible score. Decays the score if it has not been updated recently
137   // enough.
138   void AddPoints(double points);
139 
140   // Returns the total score, taking into account the base, bonus and maximum
141   // values.
142   double GetTotalScore() const;
143 
144   // Returns a structure containing the origin URL and score, and details
145   // of the base and bonus scores. Note that the |score| is limited to
146   // kMaxPoints, while the detailed scores are returned raw.
147   mojom::SiteEngagementDetails GetDetails() const;
148 
149   // Writes the values in this score into |settings_map_|.
150   void Commit();
151 
152   // Returns the discrete engagement level for this score.
153   blink::mojom::EngagementLevel GetEngagementLevel() const;
154 
155   // Returns true if the maximum number of points today has been added.
156   bool MaxPointsPerDayAdded() const;
157 
158   // Resets the score to |points| and resets the daily point limit. If
159   // |updated_time| is non-null, sets the last engagement time to that value.
160   void Reset(double points, const base::Time updated_time);
161 
162   // Get/set the last time this origin was launched from an installed shortcut.
last_shortcut_launch_time()163   base::Time last_shortcut_launch_time() const {
164     return last_shortcut_launch_time_;
165   }
set_last_shortcut_launch_time(const base::Time & time)166   void set_last_shortcut_launch_time(const base::Time& time) {
167     last_shortcut_launch_time_ = time;
168   }
169 
170   // Get/set the last time this origin recorded an engagement change.
last_engagement_time()171   base::Time last_engagement_time() const {
172     return last_engagement_time_;
173   }
set_last_engagement_time(const base::Time & time)174   void set_last_engagement_time(const base::Time& time) {
175     last_engagement_time_ = time;
176   }
177 
178  private:
179   FRIEND_TEST_ALL_PREFIXES(SiteEngagementScoreTest, FirstDailyEngagementBonus);
180   FRIEND_TEST_ALL_PREFIXES(SiteEngagementScoreTest, PartiallyEmptyDictionary);
181   FRIEND_TEST_ALL_PREFIXES(SiteEngagementScoreTest, PopulatedDictionary);
182   FRIEND_TEST_ALL_PREFIXES(SiteEngagementScoreTest, Reset);
183   friend class SiteEngagementScoreTest;
184   friend class SiteEngagementServiceTest;
185 
186   using ParamValues = std::array<std::pair<std::string, double>, MAX_VARIATION>;
187 
188   // Array holding the values corresponding to each item in Variation array.
189   static ParamValues& GetParamValues();
190 
191   // Keys used in the content settings dictionary.
192   static const char kRawScoreKey[];
193   static const char kPointsAddedTodayKey[];
194   static const char kLastEngagementTimeKey[];
195   static const char kLastShortcutLaunchTimeKey[];
196 
197   // This version of the constructor is used in unit tests.
198   SiteEngagementScore(base::Clock* clock,
199                       const GURL& origin,
200                       std::unique_ptr<base::DictionaryValue> score_dict);
201 
202   // Determine the score, accounting for any decay.
203   double DecayedScore() const;
204 
205   // Determine bonus from being installed, and having been launched recently..
206   double BonusIfShortcutLaunched() const;
207 
208   // Updates the content settings dictionary |score_dict| with the current score
209   // fields. Returns true if |score_dict| changed, otherwise return false.
210   bool UpdateScoreDict(base::DictionaryValue* score_dict);
211 
212   // The clock used to vend times. Enables time travelling in tests. Owned by
213   // the SiteEngagementService.
214   base::Clock* clock_;
215 
216   // |raw_score_| is the score before any decay is applied.
217   double raw_score_;
218 
219   // The points added 'today' are tracked to avoid adding more than
220   // kMaxPointsPerDay on any one day. 'Today' is defined in local time.
221   double points_added_today_;
222 
223   // The last time the score was updated for engagement. Used in conjunction
224   // with |points_added_today_| to avoid adding more than kMaxPointsPerDay on
225   // any one day.
226   base::Time last_engagement_time_;
227 
228   // The last time the site with this score was launched from an installed
229   // shortcut.
230   base::Time last_shortcut_launch_time_;
231 
232   // The dictionary that represents this engagement score.
233   std::unique_ptr<base::DictionaryValue> score_dict_;
234 
235   // The origin this score represents.
236   GURL origin_;
237 
238   // The settings to write this score to when Commit() is called.
239   HostContentSettingsMap* settings_map_;
240 
241   DISALLOW_COPY_AND_ASSIGN(SiteEngagementScore);
242 };
243 
244 #endif  // CHROME_BROWSER_ENGAGEMENT_SITE_ENGAGEMENT_SCORE_H_
245