Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(366)

Side by Side Diff: chrome/browser/signin/cross_device_promo.cc

Issue 1087933002: Cross Device Promo - Main Eligibility Flow (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add a flow for when the first listDevices request is scheduled in the future. Tests around the sche… Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/signin/cross_device_promo.h"
6
7 #include "base/metrics/histogram_macros.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/rand_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/time/time.h"
12 #include "chrome/common/pref_names.h"
13 #include "components/signin/core/browser/signin_client.h"
14 #include "components/signin/core/browser/signin_manager.h"
15 #include "components/signin/core/browser/signin_metrics.h"
16 #include "components/variations/variations_associated_data.h"
17 #include "net/cookies/canonical_cookie.h"
18
19 namespace {
20
21 const int kDelayUntilGettingDeviceActivityInMS = 3000;
22
23 // Helper method to set a parameter based on a particular variable from the
24 // configuration. Returns false if the parameter was not found or the
25 // converstion could not succeed.
26 bool SetParameterFromVariation(
27 std::string variation_parameter,
28 base::TimeDelta& local_parameter,
29 base::Callback<base::TimeDelta(int)> conversion) {
30 std::string parameter_as_string = variations::GetVariationParamValue(
31 "CrossDevicePromo", variation_parameter);
32 if (parameter_as_string.empty())
33 return false;
34
35 int parameter_as_int;
36 if (!base::StringToInt(parameter_as_string, &parameter_as_int))
37 return false;
38
39 local_parameter = conversion.Run(parameter_as_int);
40 return true;
41 }
42
43 } // namespace
44
45 CrossDevicePromo::CrossDevicePromo(
46 SigninManager* signin_manager,
47 GaiaCookieManagerService* cookie_manager_service,
48 SigninClient* signin_client,
49 PrefService* pref_service)
50 : signin_manager_(signin_manager),
51 cookie_manager_service_(cookie_manager_service),
52 prefs_(pref_service),
53 signin_client_(signin_client),
54 start_last_browsing_session_(base::Time()),
55 initialized_(false),
56 track_metrics_(true) {
57 Init();
58 }
59
60 CrossDevicePromo::~CrossDevicePromo() {}
61
62 void CrossDevicePromo::Shutdown() {
63 UnregisterForCookieChanges();
64 UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.BrowsingSessionDuration",
65 base::Time::Now() - start_last_browsing_session_);
66 }
67
68
69 void CrossDevicePromo::AddObserver(CrossDevicePromo::Observer* observer) {
70 observer_list_.AddObserver(observer);
71 }
72
73 void CrossDevicePromo::RemoveObserver(CrossDevicePromo::Observer* observer) {
74 observer_list_.RemoveObserver(observer);
75 }
76
77 bool CrossDevicePromo::IsPromoActive() {
78 return prefs_->GetBoolean(prefs::kCrossDevicePromoActive);
79 }
80
81 void CrossDevicePromo::Init() {
82 if (!prefs_->GetBoolean(prefs::kCrossDevicePromoOptedOut) &&
83 SetParameterFromVariation(
84 "HoursBetweenSyncDeviceChecks",
85 delay_until_next_list_devices_,
86 base::Bind(&base::TimeDelta::FromHours)) &&
87 SetParameterFromVariation(
88 "DaysToVerifySingleUserProfile",
89 single_account_in_profile_,
90 base::Bind(&base::TimeDelta::FromDays)) &&
91 SetParameterFromVariation(
92 "MinutesBetweenBrowsingSessions",
93 inactivity_between_browsing_sessions_,
94 base::Bind(&base::TimeDelta::FromMinutes)) &&
95 SetParameterFromVariation(
96 "MinutesMaxContextSwitchDuration",
97 context_switch_duration_,
98 base::Bind(&base::TimeDelta::FromMinutes))) {
99 RegisterForCookieChanges();
100 initialized_ = true;
101 UMA_HISTOGRAM_BOOLEAN("Signin.XDevicePromo.Initialized", true);
102 } else {
103 UMA_HISTOGRAM_BOOLEAN("Signin.XDevicePromo.Initialized", false);
104 }
105 }
106
107 void CrossDevicePromo::OptOut() {
108 UnregisterForCookieChanges();
109 prefs_->SetBoolean(prefs::kCrossDevicePromoOptedOut, true);
110 MarkPromoInactive();
111 }
112
113 bool CrossDevicePromo::CheckPromoEligibility() {
114 if (!initialized_) {
115 Init();
116 if (!initialized_)
117 return false;
118 }
119
120 if (prefs_->GetBoolean(prefs::kCrossDevicePromoOptedOut)) {
121 signin_metrics::LogXDevicePromoEligible(signin_metrics::OPTED_OUT);
122 return false;
123 }
124
125 if (signin_manager_->GetAuthenticatedUsername().empty()) {
126 signin_metrics::LogXDevicePromoEligible(signin_metrics::NOT_SIGNED_IN);
127 return false;
128 }
129
130 base::Time cookie_has_one_account_since = base::Time::FromInternalValue(
131 prefs_->GetInt64(
132 prefs::kCrossDevicePromoObservedSingleAccountCookie));
133 if (!prefs_->HasPrefPath(prefs::kCrossDevicePromoObservedSingleAccountCookie)
134 || cookie_has_one_account_since + single_account_in_profile_ >
135 base::Time::Now()) {
136 signin_metrics::LogXDevicePromoEligible(
137 signin_metrics::NOT_SINGLE_GAIA_ACCOUNT);
138 return false;
139 }
140
141 // This is the first time the promos being run; determine when to call the
142 // ListDevices endpoint.
143 if (!prefs_->HasPrefPath(prefs::kCrossDevicePromoNextFetchListDevicesTime)) {
144 int minutes_until_next_list_devices = base::RandGenerator(
145 delay_until_next_list_devices_.InMinutes());
146 prefs_->SetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime,
147 (base::Time::Now() +
148 base::TimeDelta::FromMinutes(minutes_until_next_list_devices)).
149 ToInternalValue());
150 signin_metrics::LogXDevicePromoEligible(
151 signin_metrics::UNKNOWN_COUNT_SYNC_DEVICES);
152 return false;
153 }
154
155 // We have no knowledge of other sync devices yet.
156 if (!prefs_->HasPrefPath(prefs::kCrossDevicePromoNumDevices)) {
157 base::Time time_next_list_devices = base::Time::FromInternalValue(
158 prefs_->GetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime));
159 // Not time yet to poll the list of devices.
160 if (time_next_list_devices > base::Time::Now()) {
161 signin_metrics::LogXDevicePromoEligible(
162 signin_metrics::UNKNOWN_COUNT_SYNC_DEVICES);
163 return false;
164 } else {
165 // We're not eligible... but might be! Track metrics in the results.
166 GetSyncDevicesForAccountInCookie();
167 return false;
168 }
169 }
170
171 int num_devices = prefs_->GetInt64(
172 prefs::kCrossDevicePromoNumDevices);
173 if (num_devices > 0) {
174 base::Time time_next_list_devices = base::Time::FromInternalValue(
175 prefs_->GetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime));
176 if (time_next_list_devices < base::Time::Now()) {
177 // Log this execution through the flow as ZERO_SYNC_DEVICES; do not log
178 // the result calculated after GetSyncDevices. We want each check for
179 // promo eligibility to show up exactly once.
180 track_metrics_ = false;
181 GetSyncDevicesForAccountInCookie();
182 }
183
184 signin_metrics::LogXDevicePromoEligible(
185 signin_metrics::ZERO_SYNC_DEVICES);
186 return false;
187 }
188
189 return true;
190 }
191
192 void CrossDevicePromo::UpdateLastActiveTime(
193 const base::Time& previous_last_active) {
194 UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.BrowsingSessionDurationComputed",
195 base::Time::Now() - previous_last_active);
196 // Check if this is a different browsing session since the last call.
197 if (base::Time::Now() - previous_last_active <=
198 inactivity_between_browsing_sessions_) {
199 return;
200 }
201
202 if (start_last_browsing_session_ != base::Time()) {
203 UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.BrowsingSessionDuration",
204 previous_last_active - start_last_browsing_session_);
205 }
206
207 start_last_browsing_session_ = base::Time::Now();
208
209 if (!CheckPromoEligibility())
210 return;
211
212 // Check if there is a record of recent browsing activity on another device.
213 // If there is none, we set a timer to update the records after a small delay
214 // to ensure server-side data is synchronized.
215 base::Time device_last_active = base::Time::FromInternalValue(
216 prefs_->GetInt64(prefs::kCrossDevicePromoLastDeviceActiveTime));
217 if (base::Time::Now() - device_last_active < context_switch_duration_) {
218 signin_metrics::LogXDevicePromoEligible(signin_metrics::ELIGIBLE);
219 MarkPromoActive();
220 return;
221 }
222
223 device_activity_timer_.Start(
224 FROM_HERE,
225 base::TimeDelta::FromMilliseconds(kDelayUntilGettingDeviceActivityInMS),
226 this,
227 &CrossDevicePromo::GetSyncDevicesForAccountInCookie);
228 }
229
230 void CrossDevicePromo::GetSyncDevicesForAccountInCookie() {
231 // Don't start a fetch while one is processing.
232 if (device_activity_fetcher_.get())
233 return;
234
235 device_activity_fetcher_.reset(
236 new DeviceActivityFetcher(signin_client_, this));
237 device_activity_fetcher_->Start();
238 }
239
240 void CrossDevicePromo::OnFetchDeviceActivitySuccess(
241 const std::vector<DeviceActivityFetcher::DeviceActivity>& devices) {
242 prefs_->SetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime,
243 (base::Time::Now() +
244 delay_until_next_list_devices_).ToInternalValue());
245 prefs_->SetInt64(prefs::kCrossDevicePromoNumDevices, devices.size());
246
247 if (devices.empty()) {
248 if (track_metrics_) {
249 signin_metrics::LogXDevicePromoEligible(
250 signin_metrics::ZERO_SYNC_DEVICES);
251 }
252 return;
253 }
254
255 base::Time most_recent_last_active = base::Time();
256 for (size_t i = 0; i < devices.size(); i++) {
257 if (devices[i].last_active > most_recent_last_active)
258 most_recent_last_active = devices[i].last_active;
259 }
260
261 prefs_->SetInt64(prefs::kCrossDevicePromoLastDeviceActiveTime,
262 most_recent_last_active.ToInternalValue());
263
264 if (base::Time::Now() - most_recent_last_active < context_switch_duration_) {
265 if (track_metrics_)
266 signin_metrics::LogXDevicePromoEligible(signin_metrics::ELIGIBLE);
267 MarkPromoActive();
268 } else {
269 if (track_metrics_) {
270 signin_metrics::LogXDevicePromoEligible(
271 signin_metrics::NO_ACTIVE_SYNC_DEVICES);
272 }
273 MarkPromoInactive();
274 }
275 track_metrics_ = true;
276 device_activity_fetcher_.reset();
277 }
278
279 void CrossDevicePromo::OnFetchDeviceActivityFailure() {
280 if (track_metrics_) {
281 signin_metrics::LogXDevicePromoEligible(
282 signin_metrics::ERROR_FETCHING_SYNC_DEVICES);
283 }
284 device_activity_fetcher_.reset();
285 }
286
287 void CrossDevicePromo::RegisterForCookieChanges() {
288 cookie_manager_service_->AddObserver(this);
289 }
290
291 void CrossDevicePromo::UnregisterForCookieChanges() {
292 cookie_manager_service_->RemoveObserver(this);
293 }
294
295 void CrossDevicePromo::OnGaiaAccountsInCookieUpdated(
296 const std::vector<std::pair<std::string, bool> >& accounts,
297 const GoogleServiceAuthError& error) {
298 VLOG(1) << "CrossDevicePromo::OnGaiaAccountsInCookieUpdated. "
299 << accounts.size() << " accounts with auth error " << error.state();
300 if (error.state() != GoogleServiceAuthError::State::NONE)
301 return;
302
303 // Multiple accounts seen - clear the pref.
304 if (accounts.size() != 1 && prefs_->HasPrefPath(
305 prefs::kCrossDevicePromoObservedSingleAccountCookie)) {
306 UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.NoLongerSingleAccount",
307 base::Time::Now() -
308 base::Time::FromInternalValue(prefs_->GetInt64(
309 prefs::kCrossDevicePromoObservedSingleAccountCookie)));
310 prefs_->ClearPref(prefs::kCrossDevicePromoObservedSingleAccountCookie);
311 // Single account seen. Note if this is the first time we've seen this.
312 } else if (accounts.size() == 1 && !prefs_->HasPrefPath(
313 prefs::kCrossDevicePromoObservedSingleAccountCookie)){
314 prefs_->SetInt64(prefs::kCrossDevicePromoObservedSingleAccountCookie,
315 base::Time::Now().ToInternalValue());
316 }
317 }
318
319 void CrossDevicePromo::MarkPromoActive() {
320 DCHECK(!prefs_->GetBoolean(prefs::kCrossDevicePromoOptedOut));
321
322 if (!prefs_->GetBoolean(prefs::kCrossDevicePromoActive)) {
323 prefs_->SetBoolean(prefs::kCrossDevicePromoActive, true);
324 FOR_EACH_OBSERVER(CrossDevicePromo::Observer,
325 observer_list_,
326 OnPromoActivationChanged(true));
327 }
328 }
329
330 void CrossDevicePromo::MarkPromoInactive() {
331 if (prefs_->GetBoolean(prefs::kCrossDevicePromoActive)) {
332 prefs_->SetBoolean(prefs::kCrossDevicePromoActive, false);
333 FOR_EACH_OBSERVER(CrossDevicePromo::Observer,
334 observer_list_,
335 OnPromoActivationChanged(false));
336 }
337 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698