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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/signin/cross_device_promo.cc
diff --git a/chrome/browser/signin/cross_device_promo.cc b/chrome/browser/signin/cross_device_promo.cc
new file mode 100644
index 0000000000000000000000000000000000000000..12ee78cffe77076a758eb16b23303bc4bb462ad0
--- /dev/null
+++ b/chrome/browser/signin/cross_device_promo.cc
@@ -0,0 +1,337 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/signin/cross_device_promo.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/prefs/pref_service.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "chrome/common/pref_names.h"
+#include "components/signin/core/browser/signin_client.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "components/signin/core/browser/signin_metrics.h"
+#include "components/variations/variations_associated_data.h"
+#include "net/cookies/canonical_cookie.h"
+
+namespace {
+
+const int kDelayUntilGettingDeviceActivityInMS = 3000;
+
+// Helper method to set a parameter based on a particular variable from the
+// configuration. Returns false if the parameter was not found or the
+// converstion could not succeed.
+bool SetParameterFromVariation(
+ std::string variation_parameter,
+ base::TimeDelta& local_parameter,
+ base::Callback<base::TimeDelta(int)> conversion) {
+ std::string parameter_as_string = variations::GetVariationParamValue(
+ "CrossDevicePromo", variation_parameter);
+ if (parameter_as_string.empty())
+ return false;
+
+ int parameter_as_int;
+ if (!base::StringToInt(parameter_as_string, &parameter_as_int))
+ return false;
+
+ local_parameter = conversion.Run(parameter_as_int);
+ return true;
+}
+
+} // namespace
+
+CrossDevicePromo::CrossDevicePromo(
+ SigninManager* signin_manager,
+ GaiaCookieManagerService* cookie_manager_service,
+ SigninClient* signin_client,
+ PrefService* pref_service)
+ : signin_manager_(signin_manager),
+ cookie_manager_service_(cookie_manager_service),
+ prefs_(pref_service),
+ signin_client_(signin_client),
+ start_last_browsing_session_(base::Time()),
+ initialized_(false),
+ track_metrics_(true) {
+ Init();
+}
+
+CrossDevicePromo::~CrossDevicePromo() {}
+
+void CrossDevicePromo::Shutdown() {
+ UnregisterForCookieChanges();
+ UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.BrowsingSessionDuration",
+ base::Time::Now() - start_last_browsing_session_);
+}
+
+
+void CrossDevicePromo::AddObserver(CrossDevicePromo::Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void CrossDevicePromo::RemoveObserver(CrossDevicePromo::Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+bool CrossDevicePromo::IsPromoActive() {
+ return prefs_->GetBoolean(prefs::kCrossDevicePromoActive);
+}
+
+void CrossDevicePromo::Init() {
+ if (!prefs_->GetBoolean(prefs::kCrossDevicePromoOptedOut) &&
+ SetParameterFromVariation(
+ "HoursBetweenSyncDeviceChecks",
+ delay_until_next_list_devices_,
+ base::Bind(&base::TimeDelta::FromHours)) &&
+ SetParameterFromVariation(
+ "DaysToVerifySingleUserProfile",
+ single_account_in_profile_,
+ base::Bind(&base::TimeDelta::FromDays)) &&
+ SetParameterFromVariation(
+ "MinutesBetweenBrowsingSessions",
+ inactivity_between_browsing_sessions_,
+ base::Bind(&base::TimeDelta::FromMinutes)) &&
+ SetParameterFromVariation(
+ "MinutesMaxContextSwitchDuration",
+ context_switch_duration_,
+ base::Bind(&base::TimeDelta::FromMinutes))) {
+ RegisterForCookieChanges();
+ initialized_ = true;
+ UMA_HISTOGRAM_BOOLEAN("Signin.XDevicePromo.Initialized", true);
+ } else {
+ UMA_HISTOGRAM_BOOLEAN("Signin.XDevicePromo.Initialized", false);
+ }
+}
+
+void CrossDevicePromo::OptOut() {
+ UnregisterForCookieChanges();
+ prefs_->SetBoolean(prefs::kCrossDevicePromoOptedOut, true);
+ MarkPromoInactive();
+}
+
+bool CrossDevicePromo::CheckPromoEligibility() {
+ if (!initialized_) {
+ Init();
+ if (!initialized_)
+ return false;
+ }
+
+ if (prefs_->GetBoolean(prefs::kCrossDevicePromoOptedOut)) {
+ signin_metrics::LogXDevicePromoEligible(signin_metrics::OPTED_OUT);
+ return false;
+ }
+
+ if (signin_manager_->GetAuthenticatedUsername().empty()) {
+ signin_metrics::LogXDevicePromoEligible(signin_metrics::NOT_SIGNED_IN);
+ return false;
+ }
+
+ base::Time cookie_has_one_account_since = base::Time::FromInternalValue(
+ prefs_->GetInt64(
+ prefs::kCrossDevicePromoObservedSingleAccountCookie));
+ if (!prefs_->HasPrefPath(prefs::kCrossDevicePromoObservedSingleAccountCookie)
+ || cookie_has_one_account_since + single_account_in_profile_ >
+ base::Time::Now()) {
+ signin_metrics::LogXDevicePromoEligible(
+ signin_metrics::NOT_SINGLE_GAIA_ACCOUNT);
+ return false;
+ }
+
+ // This is the first time the promos being run; determine when to call the
+ // ListDevices endpoint.
+ if (!prefs_->HasPrefPath(prefs::kCrossDevicePromoNextFetchListDevicesTime)) {
+ int minutes_until_next_list_devices = base::RandGenerator(
+ delay_until_next_list_devices_.InMinutes());
+ prefs_->SetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime,
+ (base::Time::Now() +
+ base::TimeDelta::FromMinutes(minutes_until_next_list_devices)).
+ ToInternalValue());
+ signin_metrics::LogXDevicePromoEligible(
+ signin_metrics::UNKNOWN_COUNT_SYNC_DEVICES);
+ return false;
+ }
+
+ // We have no knowledge of other sync devices yet.
+ if (!prefs_->HasPrefPath(prefs::kCrossDevicePromoNumDevices)) {
+ base::Time time_next_list_devices = base::Time::FromInternalValue(
+ prefs_->GetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime));
+ // Not time yet to poll the list of devices.
+ if (time_next_list_devices > base::Time::Now()) {
+ signin_metrics::LogXDevicePromoEligible(
+ signin_metrics::UNKNOWN_COUNT_SYNC_DEVICES);
+ return false;
+ } else {
+ // We're not eligible... but might be! Track metrics in the results.
+ GetSyncDevicesForAccountInCookie();
+ return false;
+ }
+ }
+
+ int num_devices = prefs_->GetInt64(
+ prefs::kCrossDevicePromoNumDevices);
+ if (num_devices > 0) {
+ base::Time time_next_list_devices = base::Time::FromInternalValue(
+ prefs_->GetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime));
+ if (time_next_list_devices < base::Time::Now()) {
+ // Log this execution through the flow as ZERO_SYNC_DEVICES; do not log
+ // the result calculated after GetSyncDevices. We want each check for
+ // promo eligibility to show up exactly once.
+ track_metrics_ = false;
+ GetSyncDevicesForAccountInCookie();
+ }
+
+ signin_metrics::LogXDevicePromoEligible(
+ signin_metrics::ZERO_SYNC_DEVICES);
+ return false;
+ }
+
+ return true;
+}
+
+void CrossDevicePromo::UpdateLastActiveTime(
+ const base::Time& previous_last_active) {
+ UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.BrowsingSessionDurationComputed",
+ base::Time::Now() - previous_last_active);
+ // Check if this is a different browsing session since the last call.
+ if (base::Time::Now() - previous_last_active <=
+ inactivity_between_browsing_sessions_) {
+ return;
+ }
+
+ if (start_last_browsing_session_ != base::Time()) {
+ UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.BrowsingSessionDuration",
+ previous_last_active - start_last_browsing_session_);
+ }
+
+ start_last_browsing_session_ = base::Time::Now();
+
+ if (!CheckPromoEligibility())
+ return;
+
+ // Check if there is a record of recent browsing activity on another device.
+ // If there is none, we set a timer to update the records after a small delay
+ // to ensure server-side data is synchronized.
+ base::Time device_last_active = base::Time::FromInternalValue(
+ prefs_->GetInt64(prefs::kCrossDevicePromoLastDeviceActiveTime));
+ if (base::Time::Now() - device_last_active < context_switch_duration_) {
+ signin_metrics::LogXDevicePromoEligible(signin_metrics::ELIGIBLE);
+ MarkPromoActive();
+ return;
+ }
+
+ device_activity_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kDelayUntilGettingDeviceActivityInMS),
+ this,
+ &CrossDevicePromo::GetSyncDevicesForAccountInCookie);
+}
+
+void CrossDevicePromo::GetSyncDevicesForAccountInCookie() {
+ // Don't start a fetch while one is processing.
+ if (device_activity_fetcher_.get())
+ return;
+
+ device_activity_fetcher_.reset(
+ new DeviceActivityFetcher(signin_client_, this));
+ device_activity_fetcher_->Start();
+}
+
+void CrossDevicePromo::OnFetchDeviceActivitySuccess(
+ const std::vector<DeviceActivityFetcher::DeviceActivity>& devices) {
+ prefs_->SetInt64(prefs::kCrossDevicePromoNextFetchListDevicesTime,
+ (base::Time::Now() +
+ delay_until_next_list_devices_).ToInternalValue());
+ prefs_->SetInt64(prefs::kCrossDevicePromoNumDevices, devices.size());
+
+ if (devices.empty()) {
+ if (track_metrics_) {
+ signin_metrics::LogXDevicePromoEligible(
+ signin_metrics::ZERO_SYNC_DEVICES);
+ }
+ return;
+ }
+
+ base::Time most_recent_last_active = base::Time();
+ for (size_t i = 0; i < devices.size(); i++) {
+ if (devices[i].last_active > most_recent_last_active)
+ most_recent_last_active = devices[i].last_active;
+ }
+
+ prefs_->SetInt64(prefs::kCrossDevicePromoLastDeviceActiveTime,
+ most_recent_last_active.ToInternalValue());
+
+ if (base::Time::Now() - most_recent_last_active < context_switch_duration_) {
+ if (track_metrics_)
+ signin_metrics::LogXDevicePromoEligible(signin_metrics::ELIGIBLE);
+ MarkPromoActive();
+ } else {
+ if (track_metrics_) {
+ signin_metrics::LogXDevicePromoEligible(
+ signin_metrics::NO_ACTIVE_SYNC_DEVICES);
+ }
+ MarkPromoInactive();
+ }
+ track_metrics_ = true;
+ device_activity_fetcher_.reset();
+}
+
+void CrossDevicePromo::OnFetchDeviceActivityFailure() {
+ if (track_metrics_) {
+ signin_metrics::LogXDevicePromoEligible(
+ signin_metrics::ERROR_FETCHING_SYNC_DEVICES);
+ }
+ device_activity_fetcher_.reset();
+}
+
+void CrossDevicePromo::RegisterForCookieChanges() {
+ cookie_manager_service_->AddObserver(this);
+}
+
+void CrossDevicePromo::UnregisterForCookieChanges() {
+ cookie_manager_service_->RemoveObserver(this);
+}
+
+void CrossDevicePromo::OnGaiaAccountsInCookieUpdated(
+ const std::vector<std::pair<std::string, bool> >& accounts,
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "CrossDevicePromo::OnGaiaAccountsInCookieUpdated. "
+ << accounts.size() << " accounts with auth error " << error.state();
+ if (error.state() != GoogleServiceAuthError::State::NONE)
+ return;
+
+ // Multiple accounts seen - clear the pref.
+ if (accounts.size() != 1 && prefs_->HasPrefPath(
+ prefs::kCrossDevicePromoObservedSingleAccountCookie)) {
+ UMA_HISTOGRAM_TIMES("Signin.XDevicePromo.NoLongerSingleAccount",
+ base::Time::Now() -
+ base::Time::FromInternalValue(prefs_->GetInt64(
+ prefs::kCrossDevicePromoObservedSingleAccountCookie)));
+ prefs_->ClearPref(prefs::kCrossDevicePromoObservedSingleAccountCookie);
+ // Single account seen. Note if this is the first time we've seen this.
+ } else if (accounts.size() == 1 && !prefs_->HasPrefPath(
+ prefs::kCrossDevicePromoObservedSingleAccountCookie)){
+ prefs_->SetInt64(prefs::kCrossDevicePromoObservedSingleAccountCookie,
+ base::Time::Now().ToInternalValue());
+ }
+}
+
+void CrossDevicePromo::MarkPromoActive() {
+ DCHECK(!prefs_->GetBoolean(prefs::kCrossDevicePromoOptedOut));
+
+ if (!prefs_->GetBoolean(prefs::kCrossDevicePromoActive)) {
+ prefs_->SetBoolean(prefs::kCrossDevicePromoActive, true);
+ FOR_EACH_OBSERVER(CrossDevicePromo::Observer,
+ observer_list_,
+ OnPromoActivationChanged(true));
+ }
+}
+
+void CrossDevicePromo::MarkPromoInactive() {
+ if (prefs_->GetBoolean(prefs::kCrossDevicePromoActive)) {
+ prefs_->SetBoolean(prefs::kCrossDevicePromoActive, false);
+ FOR_EACH_OBSERVER(CrossDevicePromo::Observer,
+ observer_list_,
+ OnPromoActivationChanged(false));
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698