1 // Copyright 2015 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.
5 #ifndef CHROME_BROWSER_SIGNIN_CROSS_DEVICE_PROMO_H_
6 #define CHROME_BROWSER_SIGNIN_CROSS_DEVICE_PROMO_H_
8 #include "base/observer_list.h"
9 #include "base/timer/timer.h"
10 #include "components/keyed_service/core/keyed_service.h"
11 #include "components/signin/core/browser/device_activity_fetcher.h"
12 #include "components/signin/core/browser/gaia_cookie_manager_service.h"
18 // The Cross Device Promo promotes Chrome Signin within a profile where there is
19 // one GAIA account signed in to the content area for a long period of time
20 // (indicating that this profile is used by only one user) and where that
21 // account uses Chrome Sync on other devices but is not signed in here.
23 // This class determines whether the above criteria have been met and thus
24 // whether the promo should be displayed. This class imposes additional criteria
25 // such that the promo is only displayed if there is sufficiently recent
26 // activity on another device, indicating the user is switching contexts but
27 // continuing their browsing activity. This class's behavior is controlled by
28 // the "CrossDevicePromo" field trial.
29 // The UI is defined elsewhere.
31 // This class implements the preferences necessary to track whether a profile
32 // meets the above criteria, and whether the user has permanently opted out of
35 // The class relies on the GaiaCookieManagerService to determine if there has
36 // been a single GAIA account signed in to the content area for a long period of
37 // time, and relies on the DeviceActivityFetcher to determine if that account
38 // uses Chrome Sync on other devices.
39 class CrossDevicePromo
: public KeyedService
,
40 public GaiaCookieManagerService::Observer
,
41 public DeviceActivityFetcher::Observer
{
45 // Called when the profile moves from being ineligible to eligible for the
46 // promo or vice versa; the new state is noted in the |eligible| parameter.
47 virtual void OnPromoEligibilityChanged(bool eligible
) = 0;
50 // The following constants are the parameters for a particular experiment
51 // within the field trial that controls this class's behaviors.
52 static const char kCrossDevicePromoFieldTrial
[];
53 // This field trial parameter specifies how often the device activity should
55 static const char kParamHoursBetweenDeviceActivityChecks
[];
56 // This field trial parameter defines for how long the profile's cookie must
57 // contain exactly one GAIA account before the profile is considered
59 static const char kParamDaysToVerifySingleUserProfile
[];
60 // This field trial parameter defines how much time must pass between calls
61 // to MaybeBrowsingSessionStarted() before the code considers a new browsing
62 // session to have started and re-evaluates if the promo should be shown.
63 static const char kParamMinutesBetweenBrowsingSessions
[];
64 // This field trial parameter defines how much time may pass between the last
65 // observed device activity and the start of a new browsing session for the
66 // promo to consider the new browsing session to be a context switch.
67 static const char kParamMinutesMaxContextSwitchDuration
[];
68 // This field trial parameter defines what percentage of all device activity
69 // fetches should be not be executed, to throttle requests to the server.
70 static const char kParamRPCThrottle
[];
72 // Constructor takes non-null pointers to required services. This object does
73 // not take ownership of any of the passed objects. This also calls Init().
74 explicit CrossDevicePromo(SigninManager
* signin_manager
,
75 GaiaCookieManagerService
* cookie_manager_service
,
76 SigninClient
* signin_client
,
77 PrefService
* pref_service
);
78 ~CrossDevicePromo() override
;
81 // Ends observation of other services and records the length of any current
82 // browsing session (see signin_metrics::LogBrowsingSessionDuration()). This
83 // is called only during Chrome shutdown.
84 void Shutdown() override
;
86 // GaiaCookieManagerService::Observer:
87 // This supports monitoring whether the content area is signed into exactly
88 // one GAIA account for a long period of time. This tracks the earliest time
89 // |accounts| contained (and still contains) exactly one account, so that
90 // other methods can use kParamDaysToVerifySingleUserProfile to verify if this
91 // Profile is considered single-user.
92 void OnGaiaAccountsInCookieUpdated(
93 const std::vector
<gaia::ListedAccount
>& accounts
,
94 const GoogleServiceAuthError
& error
) override
;
96 // DeviceActivityFetcher::Observer:
97 // OnFetchDeviceActivity* are called from |device_activity_fetcher_| which
98 // was created in GetDevicesActivityForGAIAAccountInCookieJar(). Deletes
99 // |device_activity_fetcher_| at the end of the method.
100 // See DetermineEligibilityFromDeviceActivity() for details.
101 void OnFetchDeviceActivitySuccess(
102 const std::vector
<DeviceActivityFetcher::DeviceActivity
>& devices
)
104 void OnFetchDeviceActivityFailure() override
;
106 // Callable by third parties to register or unregister for callbacks when the
107 // promo's eligibility-to-be-shown state changes.
108 void AddObserver(CrossDevicePromo::Observer
* observer
);
109 void RemoveObserver(CrossDevicePromo::Observer
* observer
);
111 // Returns whether the profile has been marked as eligible to be shown the
113 bool ShouldShowPromo() const;
115 // Called when the user requests to opt out of the promo. This will set a pref
116 // that forever marks the profile ineligible for the promo.
119 // Called whenever a browser becomes active. Notes the start of a new browsing
120 // session if the last call to this method (noted in |previous_last_active|)
121 // was more than |inactivity_between_browsing_sessions_| ago. For new browsing
122 // sessions, will either determine if the promo is eligible to be shown, or
123 // will use |device_activity_timer_| to schedule getting more information with
124 // GetDevicesActivityForGAIAAccountInCookieJar().
125 void MaybeBrowsingSessionStarted(const base::Time
& previous_last_active
);
127 // Called only in tests; calls Init() if not already initialized. See comments
128 // on |initialized_| for details.
129 bool CheckPromoEligibilityForTesting();
132 // Initializes configuration parameters from the "CrossDevicePromo" field
133 // trial and registers for changes to the relevant GAIA cookie. In tests, this
134 // may be called more than once; see |initialized_| for details.
137 // Called when the determination of whether to show the promo has been made.
138 // This both stores that decision and notifies all registered observers of any
140 void MarkPromoShouldBeShown();
141 void MarkPromoShouldNotBeShown();
143 // Performs all checks to determine if this profile could be shown the promo
144 // except for initiating a fetch for additional data. This will return false
145 // if the data available locally indicates the profile should not be shown
146 // the promo; returns true if the profile could be shown the promo (even if
147 // additional checks are to be performed).
148 bool CheckPromoEligibility();
150 // Called whenever new device activity is available. Checks that there is at
151 // least one device that had activity within the past
152 // kParamMinutesMaxContextSwitchDuration to determine if the promo should be
153 // shown. Once determined, the MarkPromoShould[Not]BeShown() method is called.
154 // Note that if the device is in a context switch, a followup call to
155 // GetDevicesActivityForGAIAAccountInCookieJar() will be scheduled for when
156 // the context switch would expire.
157 void DetermineEligibilityFromDeviceActivity(
158 const std::vector
<DeviceActivityFetcher::DeviceActivity
>& devices
);
160 // Helpers to get and set the time value stored in a pref.
161 base::Time
GetTimePref(const std::string
& pref
) const;
162 void SetTimePref(const std::string
& pref
, const base::Time
& value
);
164 // Performs checks if the promo is eligible to be displayed to this profile.
165 // This will not write any prefs or initiate any checks that are otherwise
166 // called in CheckPromoEligibility(). Records no metrics. Used for DCHECKs.
167 bool VerifyPromoEligibleReadOnly() const;
169 // Adds or removes |this| as an observer of |cookie_manager_service_|.
170 // We observe the |cookie_manager_service_| for its lifetime.
171 void RegisterForCookieChanges();
172 void UnregisterForCookieChanges();
174 // Creates a new DeviceActivityFetcher to get the list of devices, and the
175 // details of the devices (see DeviceActivityFetcher.h) where the GAIA account
176 // in this profile's cookie jar is signed in to Chrome Sync.
177 // If a |device_activity_fetcher_| is already executing a fetch, this method
178 // will not start a second fetch, as the results would be the same.
179 void GetDevicesActivityForGAIAAccountInCookieJar();
181 // Set by Init() to indicate if the promo service has been successfully
182 // initialized. Initialization will not occur if the user has previously opted
183 // out of the promo. Also, successful initialization requires all necessary
184 // parameters that control the promo to be read from the field trial.
185 // In testing an initial call to Init() may fail and a future call may succeed
186 // (see Init()); in non-test scenarios, however, failed initialization is
187 // unrecoverable and future calls to other class methods should be no-ops.
190 // These four pointers are weak pointers; they are not owned by this object
191 // and will outlive this object.
192 SigninManager
* signin_manager_
;
193 GaiaCookieManagerService
* cookie_manager_service_
;
195 SigninClient
* signin_client_
;
197 scoped_ptr
<DeviceActivityFetcher
> device_activity_fetcher_
;
198 base::ObserverList
<CrossDevicePromo::Observer
> observer_list_
;
200 // Initialized from the |kParamMinutesMaxContextSwitchDuration| field trial
201 // parameter. See |kParamMinutesMaxContextSwitchDuration| for details.
202 base::TimeDelta context_switch_duration_
;
204 // If the device activity has never been fetched, the delay until the check
205 // will be a random duration between zero and
206 // |kParamHoursBetweenDeviceActivityChecks|. For all other
207 // fetches, the delay will be |kParamHoursBetweenDeviceActivityChecks|. See
208 // |kParamHoursBetweenDeviceActivityChecks| for details.
209 base::TimeDelta delay_until_next_device_activity_fetch_
;
211 // Initialized from the |kParamDaysToVerifySingleUserProfile| field trial
212 // parameter. See |kParamDaysToVerifySingleUserProfile| for details.
213 base::TimeDelta single_account_duration_threshold_
;
215 // Initialized from the |kParamMinutesBetweenBrowsingSessions| field trial
216 // parameter. See |kParamMinutesBetweenBrowsingSessions| for details.
217 base::TimeDelta inactivity_between_browsing_sessions_
;
219 // Randomly initialized from the |kParamRPCThrottle| field trial parameter.
220 // See |kParamRPCThrottle| for details. If true, |device_activity_fetcher_|
221 // should never be initialized.
224 // Metric to help us track how long a browsing session is. This is set in
225 // MaybeBrowsingSessionStarted(), see that method for details.
226 // Useful for configuring the field trial to manage our server quota.
227 base::Time start_last_browsing_session_
;
229 // Used to delay the check of device activity. See
230 // OnFetchDeviceActivitySuccess() or MaybeBrowsingSessionStarted(), as well as
231 // |delay_until_next_device_activity_fetch_|, for details.
232 base::OneShotTimer
<CrossDevicePromo
> device_activity_timer_
;
234 DISALLOW_COPY_AND_ASSIGN(CrossDevicePromo
);
237 #endif // CHROME_BROWSER_SIGNIN_CROSS_DEVICE_PROMO_H_