blob: 276a038e6a4e20cef8503e302217bc8bb390dbad [file] [log] [blame]
// Copyright 2014 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/ui/passwords/password_bubble_experiment.h"
#include "base/metrics/field_trial.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/pref_registry/pref_registry_syncable.h"
#include "components/variations/variations_associated_data.h"
namespace password_bubble_experiment {
namespace {
bool IsNegativeEvent(password_manager::metrics_util::UIDismissalReason reason) {
return (reason == password_manager::metrics_util::NO_DIRECT_INTERACTION ||
reason == password_manager::metrics_util::CLICKED_NOPE ||
reason == password_manager::metrics_util::CLICKED_NEVER);
}
// "TimeSpan" experiment -----------------------------------------------------
bool ExtractTimeSpanParams(base::TimeDelta* time_delta, int* nopes_limit) {
std::map<std::string, std::string> params;
bool retrieved = variations::GetVariationParams(kExperimentName, &params);
if (!retrieved)
return false;
int days = 0;
if (!base::StringToInt(params[kParamTimeSpan], &days) ||
!base::StringToInt(params[kParamTimeSpanNopeThreshold], nopes_limit))
return false;
*time_delta = base::TimeDelta::FromDays(days);
return true;
}
bool OverwriteTimeSpanPrefsIfNeeded(PrefService* prefs,
base::TimeDelta time_span) {
base::Time beginning = base::Time::FromInternalValue(
prefs->GetInt64(prefs::kPasswordBubbleTimeStamp));
base::Time now = base::Time::Now();
if (beginning + time_span < now) {
prefs->SetInt64(prefs::kPasswordBubbleTimeStamp, now.ToInternalValue());
prefs->SetInteger(prefs::kPasswordBubbleNopesCount, 0);
return true;
}
return false;
}
// If user dismisses the bubble >= kParamTimeSpanNopeThreshold times during
// kParamTimeSpan days then the bubble isn't shown until the end of this time
// span.
bool ShouldShowBubbleTimeSpanExperiment(PrefService* prefs) {
base::TimeDelta time_span;
int nopes_limit = 0;
if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) {
VLOG(2) << "Can't read parameters for "
<< kExperimentName << " experiment";
return true;
}
// Check if the new time span has started.
if (OverwriteTimeSpanPrefsIfNeeded(prefs, time_span))
return true;
int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount);
return current_nopes < nopes_limit;
}
// Increase the "Nope" counter in prefs and start a new time span if needed.
void UpdateTimeSpanPrefs(
PrefService* prefs,
password_manager::metrics_util::UIDismissalReason reason) {
if (!IsNegativeEvent(reason))
return;
base::TimeDelta time_span;
int nopes_limit = 0;
if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) {
VLOG(2) << "Can't read parameters for "
<< kExperimentName << " experiment";
return;
}
OverwriteTimeSpanPrefsIfNeeded(prefs, time_span);
int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount);
prefs->SetInteger(prefs::kPasswordBubbleNopesCount, current_nopes + 1);
}
// "Probability" experiment --------------------------------------------------
bool ExtractProbabilityParams(unsigned* history_length, unsigned* saves) {
std::map<std::string, std::string> params;
bool retrieved = variations::GetVariationParams(kExperimentName, &params);
if (!retrieved)
return false;
return base::StringToUint(params[kParamProbabilityInteractionsCount],
history_length) &&
base::StringToUint(params[kParamProbabilityFakeSaves], saves);
}
std::vector<int> ReadInteractionHistory(PrefService* prefs) {
std::vector<int> interactions;
const base::ListValue* list =
prefs->GetList(prefs::kPasswordBubbleLastInteractions);
if (!list)
return interactions;
for (const base::Value* value : *list) {
int out_value;
if (value->GetAsInteger(&out_value))
interactions.push_back(out_value);
}
return interactions;
}
// We keep the history of last kParamProbabilityInteractionsCount interactions
// with the bubble. We implicitly add kParamProbabilityFakeSaves "Save" clicks.
// If there are x "Save" clicks among those kParamProbabilityInteractionsCount
// then the bubble is shown with probability (x + kParamProbabilityFakeSaves)/
// (kParamProbabilityInteractionsCount + kParamProbabilityFakeSaves).
bool ShouldShowBubbleProbabilityExperiment(PrefService* prefs) {
unsigned history_length = 0, fake_saves = 0;
if (!ExtractProbabilityParams(&history_length, &fake_saves)) {
VLOG(2) << "Can't read parameters for "
<< kExperimentName << " experiment";
return true;
}
std::vector<int> interactions = ReadInteractionHistory(prefs);
unsigned real_saves =
std::count(interactions.begin(), interactions.end(),
password_manager::metrics_util::CLICKED_SAVE);
return (interactions.size() + fake_saves) * base::RandDouble() <=
real_saves + fake_saves;
}
void UpdateProbabilityPrefs(
PrefService* prefs,
password_manager::metrics_util::UIDismissalReason reason) {
if (!IsNegativeEvent(reason) &&
reason != password_manager::metrics_util::CLICKED_SAVE)
return;
unsigned history_length = 0, fake_saves = 0;
if (!ExtractProbabilityParams(&history_length, &fake_saves)) {
VLOG(2) << "Can't read parameters for "
<< kExperimentName << " experiment";
return;
}
std::vector<int> interactions = ReadInteractionHistory(prefs);
interactions.push_back(reason);
size_t history_beginning = interactions.size() > history_length ?
interactions.size() - history_length : 0;
base::ListValue value;
for (size_t i = history_beginning; i < interactions.size(); ++i)
value.AppendInteger(interactions[i]);
prefs->Set(prefs::kPasswordBubbleLastInteractions, value);
}
} // namespace
const char kExperimentName[] = "PasswordBubbleAlgorithm";
const char kGroupTimeSpanBased[] = "TimeSpan";
const char kGroupProbabilityBased[] = "Probability";
const char kParamProbabilityFakeSaves[] = "saves_count";
const char kParamProbabilityInteractionsCount[] = "last_interactions_count";
const char kParamTimeSpan[] = "time_span";
const char kParamTimeSpanNopeThreshold[] = "nope_threshold";
void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterInt64Pref(
prefs::kPasswordBubbleTimeStamp,
0,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
registry->RegisterIntegerPref(
prefs::kPasswordBubbleNopesCount,
0,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
registry->RegisterListPref(
prefs::kPasswordBubbleLastInteractions,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
bool ShouldShowBubble(PrefService* prefs) {
if (!base::FieldTrialList::TrialExists(kExperimentName))
return true;
std::string group_name =
base::FieldTrialList::FindFullName(kExperimentName);
if (group_name == kGroupTimeSpanBased) {
return ShouldShowBubbleTimeSpanExperiment(prefs);
}
if (group_name == kGroupProbabilityBased) {
return ShouldShowBubbleProbabilityExperiment(prefs);
}
// The "Show Always" should be the default case.
return true;
}
void RecordBubbleClosed(
PrefService* prefs,
password_manager::metrics_util::UIDismissalReason reason) {
UpdateTimeSpanPrefs(prefs, reason);
UpdateProbabilityPrefs(prefs, reason);
}
} // namespace password_bubble_experiment