OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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/ui/passwords/password_bubble_experiment.h" |
| 6 |
| 7 #include "base/metrics/field_trial.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/pref_registry/pref_registry_syncable.h" |
| 14 #include "components/variations/variations_associated_data.h" |
| 15 |
| 16 namespace password_bubble_experiment { |
| 17 namespace { |
| 18 |
| 19 bool IsNegativeEvent(password_manager::metrics_util::UIDismissalReason reason) { |
| 20 return (reason == password_manager::metrics_util::NO_DIRECT_INTERACTION || |
| 21 reason == password_manager::metrics_util::CLICKED_NOPE || |
| 22 reason == password_manager::metrics_util::CLICKED_NEVER); |
| 23 } |
| 24 |
| 25 // "TimeSpan" experiment ----------------------------------------------------- |
| 26 |
| 27 bool ExtractTimeSpanParams(base::TimeDelta* time_delta, int* nopes_limit) { |
| 28 std::map<std::string, std::string> params; |
| 29 bool retrieved = variations::GetVariationParams(kExperimentName, ¶ms); |
| 30 if (!retrieved) |
| 31 return false; |
| 32 int days = 0; |
| 33 if (!base::StringToInt(params[kParamTimeSpan], &days) || |
| 34 !base::StringToInt(params[kParamTimeSpanNopeThreshold], nopes_limit)) |
| 35 return false; |
| 36 *time_delta = base::TimeDelta::FromDays(days); |
| 37 return true; |
| 38 } |
| 39 |
| 40 bool OverwriteTimeSpanPrefsIfNeeded(PrefService* prefs, |
| 41 base::TimeDelta time_span) { |
| 42 base::Time beginning = base::Time::FromInternalValue( |
| 43 prefs->GetInt64(prefs::kPasswordBubbleTimeStamp)); |
| 44 base::Time now = base::Time::Now(); |
| 45 if (beginning + time_span < now) { |
| 46 prefs->SetInt64(prefs::kPasswordBubbleTimeStamp, now.ToInternalValue()); |
| 47 prefs->SetInteger(prefs::kPasswordBubbleNopesCount, 0); |
| 48 return true; |
| 49 } |
| 50 return false; |
| 51 } |
| 52 |
| 53 // If user dismisses the bubble >= kParamTimeSpanNopeThreshold times during |
| 54 // kParamTimeSpan days then the bubble isn't shown until the end of this time |
| 55 // span. |
| 56 bool ShouldShowBubbleTimeSpanExperiment(PrefService* prefs) { |
| 57 base::TimeDelta time_span; |
| 58 int nopes_limit = 0; |
| 59 if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) { |
| 60 VLOG(2) << "Can't read parameters for " |
| 61 << kExperimentName << " experiment"; |
| 62 return true; |
| 63 } |
| 64 // Check if the new time span has started. |
| 65 if (OverwriteTimeSpanPrefsIfNeeded(prefs, time_span)) |
| 66 return true; |
| 67 int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount); |
| 68 return current_nopes < nopes_limit; |
| 69 } |
| 70 |
| 71 // Increase the "Nope" counter in prefs and start a new time span if needed. |
| 72 void UpdateTimeSpanPrefs( |
| 73 PrefService* prefs, |
| 74 password_manager::metrics_util::UIDismissalReason reason) { |
| 75 if (!IsNegativeEvent(reason)) |
| 76 return; |
| 77 base::TimeDelta time_span; |
| 78 int nopes_limit = 0; |
| 79 if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) { |
| 80 VLOG(2) << "Can't read parameters for " |
| 81 << kExperimentName << " experiment"; |
| 82 return; |
| 83 } |
| 84 OverwriteTimeSpanPrefsIfNeeded(prefs, time_span); |
| 85 int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount); |
| 86 prefs->SetInteger(prefs::kPasswordBubbleNopesCount, current_nopes + 1); |
| 87 } |
| 88 |
| 89 // "Probability" experiment -------------------------------------------------- |
| 90 |
| 91 bool ExtractProbabilityParams(unsigned* history_length, unsigned* saves) { |
| 92 std::map<std::string, std::string> params; |
| 93 bool retrieved = variations::GetVariationParams(kExperimentName, ¶ms); |
| 94 if (!retrieved) |
| 95 return false; |
| 96 return base::StringToUint(params[kParamProbabilityInteractionsCount], |
| 97 history_length) && |
| 98 base::StringToUint(params[kParamProbabilityFakeSaves], saves); |
| 99 } |
| 100 |
| 101 std::vector<int> ReadInteractionHistory(PrefService* prefs) { |
| 102 std::vector<int> interactions; |
| 103 const base::ListValue* list = |
| 104 prefs->GetList(prefs::kPasswordBubbleLastInteractions); |
| 105 if (!list) |
| 106 return interactions; |
| 107 for (const base::Value* value : *list) { |
| 108 int out_value; |
| 109 if (value->GetAsInteger(&out_value)) |
| 110 interactions.push_back(out_value); |
| 111 } |
| 112 return interactions; |
| 113 } |
| 114 |
| 115 // We keep the history of last kParamProbabilityInteractionsCount interactions |
| 116 // with the bubble. We implicitly add kParamProbabilityFakeSaves "Save" clicks. |
| 117 // If there are x "Save" clicks among those kParamProbabilityInteractionsCount |
| 118 // then the bubble is shown with probability (x + kParamProbabilityFakeSaves)/ |
| 119 // (kParamProbabilityInteractionsCount + kParamProbabilityFakeSaves). |
| 120 bool ShouldShowBubbleProbabilityExperiment(PrefService* prefs) { |
| 121 unsigned history_length = 0, fake_saves = 0; |
| 122 if (!ExtractProbabilityParams(&history_length, &fake_saves)) { |
| 123 VLOG(2) << "Can't read parameters for " |
| 124 << kExperimentName << " experiment"; |
| 125 return true; |
| 126 } |
| 127 std::vector<int> interactions = ReadInteractionHistory(prefs); |
| 128 unsigned real_saves = |
| 129 std::count(interactions.begin(), interactions.end(), |
| 130 password_manager::metrics_util::CLICKED_SAVE); |
| 131 return (interactions.size() + fake_saves) * base::RandDouble() <= |
| 132 real_saves + fake_saves; |
| 133 } |
| 134 |
| 135 void UpdateProbabilityPrefs( |
| 136 PrefService* prefs, |
| 137 password_manager::metrics_util::UIDismissalReason reason) { |
| 138 if (!IsNegativeEvent(reason) && |
| 139 reason != password_manager::metrics_util::CLICKED_SAVE) |
| 140 return; |
| 141 unsigned history_length = 0, fake_saves = 0; |
| 142 if (!ExtractProbabilityParams(&history_length, &fake_saves)) { |
| 143 VLOG(2) << "Can't read parameters for " |
| 144 << kExperimentName << " experiment"; |
| 145 return; |
| 146 } |
| 147 std::vector<int> interactions = ReadInteractionHistory(prefs); |
| 148 interactions.push_back(reason); |
| 149 size_t history_beginning = interactions.size() > history_length ? |
| 150 interactions.size() - history_length : 0; |
| 151 base::ListValue value; |
| 152 for (size_t i = history_beginning; i < interactions.size(); ++i) |
| 153 value.AppendInteger(interactions[i]); |
| 154 prefs->Set(prefs::kPasswordBubbleLastInteractions, value); |
| 155 } |
| 156 |
| 157 } // namespace |
| 158 |
| 159 const char kExperimentName[] = "PasswordBubbleAlgorithm"; |
| 160 const char kGroupTimeSpanBased[] = "TimeSpan"; |
| 161 const char kGroupProbabilityBased[] = "Probability"; |
| 162 const char kParamProbabilityFakeSaves[] = "saves_count"; |
| 163 const char kParamProbabilityInteractionsCount[] = "last_interactions_count"; |
| 164 const char kParamTimeSpan[] = "time_span"; |
| 165 const char kParamTimeSpanNopeThreshold[] = "nope_threshold"; |
| 166 |
| 167 void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) { |
| 168 registry->RegisterInt64Pref( |
| 169 prefs::kPasswordBubbleTimeStamp, |
| 170 0, |
| 171 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| 172 registry->RegisterIntegerPref( |
| 173 prefs::kPasswordBubbleNopesCount, |
| 174 0, |
| 175 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| 176 registry->RegisterListPref( |
| 177 prefs::kPasswordBubbleLastInteractions, |
| 178 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| 179 } |
| 180 |
| 181 bool ShouldShowBubble(PrefService* prefs) { |
| 182 if (!base::FieldTrialList::TrialExists(kExperimentName)) |
| 183 return true; |
| 184 std::string group_name = |
| 185 base::FieldTrialList::FindFullName(kExperimentName); |
| 186 |
| 187 if (group_name == kGroupTimeSpanBased) { |
| 188 return ShouldShowBubbleTimeSpanExperiment(prefs); |
| 189 } |
| 190 if (group_name == kGroupProbabilityBased) { |
| 191 return ShouldShowBubbleProbabilityExperiment(prefs); |
| 192 } |
| 193 |
| 194 // The "Show Always" should be the default case. |
| 195 return true; |
| 196 } |
| 197 |
| 198 void RecordBubbleClosed( |
| 199 PrefService* prefs, |
| 200 password_manager::metrics_util::UIDismissalReason reason) { |
| 201 UpdateTimeSpanPrefs(prefs, reason); |
| 202 UpdateProbabilityPrefs(prefs, reason); |
| 203 } |
| 204 |
| 205 } // namespace password_bubble_experiment |
OLD | NEW |