blob: 4bc8bda5cfd252123975c523f93c917f7d5bd9af [file] [log] [blame]
// Copyright 2021 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 "components/accuracy_tips/accuracy_service.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_forward.h"
#include "base/containers/contains.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "components/accuracy_tips/accuracy_tip_safe_browsing_client.h"
#include "components/accuracy_tips/accuracy_tip_status.h"
#include "components/accuracy_tips/accuracy_tip_ui.h"
#include "components/accuracy_tips/features.h"
#include "components/accuracy_tips/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "url/gurl.h"
namespace accuracy_tips {
// Returns a suffix for accuracy tips histograms.
// Needs to match AccuracyTipInteractions from histogram_suffixes_list.xml.
const std::string GetHistogramSuffix(AccuracyTipUI::Interaction interaction) {
switch (interaction) {
case AccuracyTipUI::Interaction::kNoAction:
return "NoAction";
case AccuracyTipUI::Interaction::kLearnMore:
return "LearnMore";
case AccuracyTipUI::Interaction::kOptOut:
return "OptOut";
case AccuracyTipUI::Interaction::kClosed:
return "Closed";
case AccuracyTipUI::Interaction::kDisabledByExperiment:
NOTREACHED(); // We don't need specific histograms for this.
return "";
}
}
// Returns the preference to store feature state in. Uses a different pref if
// the UI is disabled to avoid dark-launch experiments affecting real usage.
const char* GetLastShownPrefName(bool disable_ui) {
return disable_ui ? prefs::kLastAccuracyTipShownDisabledUi
: prefs::kLastAccuracyTipShown;
}
const char* GetPreviousInteractionsPrefName(bool disable_ui) {
return disable_ui ? prefs::kPreviousInteractionsDisabledUi
: prefs::kPreviousInteractions;
}
using AccuracyCheckCallback = AccuracyService::AccuracyCheckCallback;
// static
void AccuracyService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
const bool disable_ui = features::kDisableUi.Get();
registry->RegisterTimePref(GetLastShownPrefName(disable_ui), base::Time());
registry->RegisterListPref(GetPreviousInteractionsPrefName(disable_ui));
}
AccuracyService::AccuracyService(
std::unique_ptr<AccuracyTipUI> ui,
PrefService* pref_service,
scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> sb_database,
scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
scoped_refptr<base::SequencedTaskRunner> io_task_runner)
: ui_(std::move(ui)),
pref_service_(pref_service),
ui_task_runner_(ui_task_runner),
io_task_runner_(io_task_runner),
sample_url_(GURL(features::kSampleUrl.Get())),
time_between_prompts_(features::kTimeBetweenPrompts.Get()),
disable_ui_(features::kDisableUi.Get()) {
if (sb_database) {
sb_client_ = base::MakeRefCounted<AccuracyTipSafeBrowsingClient>(
std::move(sb_database), std::move(ui_task_runner),
std::move(io_task_runner));
}
}
AccuracyService::~AccuracyService() = default;
void AccuracyService::Shutdown() {
if (sb_client_) {
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&AccuracyTipSafeBrowsingClient::ShutdownOnIOThread,
std::move(sb_client_)));
}
}
void AccuracyService::CheckAccuracyStatus(const GURL& url,
AccuracyCheckCallback callback) {
DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
const base::Value* last_interactions =
pref_service_->Get(GetPreviousInteractionsPrefName(disable_ui_));
const base::Value opt_out_value(
static_cast<int>(AccuracyTipUI::Interaction::kOptOut));
if (base::Contains(last_interactions->GetList(), opt_out_value)) {
return std::move(callback).Run(AccuracyTipStatus::kOptOut);
}
base::Time last_shown =
pref_service_->GetTime(GetLastShownPrefName(disable_ui_));
if (clock_->Now() - last_shown < time_between_prompts_) {
return std::move(callback).Run(AccuracyTipStatus::kRateLimited);
}
if (sample_url_.is_valid() && url == sample_url_) {
return std::move(callback).Run(AccuracyTipStatus::kShowAccuracyTip);
}
if (!sb_client_) {
return std::move(callback).Run(AccuracyTipStatus::kNone);
}
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&AccuracyTipSafeBrowsingClient::CheckAccuracyStatusOnIOThread,
sb_client_, url, std::move(callback)));
}
void AccuracyService::MaybeShowAccuracyTip(content::WebContents* web_contents) {
DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
pref_service_->SetTime(GetLastShownPrefName(disable_ui_), clock_->Now());
if (disable_ui_) {
return OnAccuracyTipClosed(
base::TimeTicks(), AccuracyTipUI::Interaction::kDisabledByExperiment);
}
ui_->ShowAccuracyTip(
web_contents, AccuracyTipStatus::kShowAccuracyTip,
base::BindOnce(&AccuracyService::OnAccuracyTipClosed,
weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
}
void AccuracyService::OnAccuracyTipClosed(
base::TimeTicks time_opened,
AccuracyTipUI::Interaction interaction) {
DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
ListPrefUpdate update(pref_service_,
GetPreviousInteractionsPrefName(disable_ui_));
base::Value* interaction_list = update.Get();
interaction_list->Append(static_cast<int>(interaction));
// Record metrics.
base::UmaHistogramEnumeration("Privacy.AccuracyTip.AccuracyTipInteraction",
interaction);
base::UmaHistogramCounts100("Privacy.AccuracyTip.NumDialogsShown",
interaction_list->GetList().size());
if (interaction != AccuracyTipUI::Interaction::kDisabledByExperiment) {
const base::TimeDelta time_open = base::TimeTicks::Now() - time_opened;
base::UmaHistogramMediumTimes("Privacy.AccuracyTip.AccuracyTipTimeOpen",
time_open);
const std::string suffix = GetHistogramSuffix(interaction);
base::UmaHistogramCounts100("Privacy.AccuracyTip.NumDialogsShown." + suffix,
interaction_list->GetList().size());
base::UmaHistogramMediumTimes(
"Privacy.AccuracyTip.AccuracyTipTimeOpen." + suffix, time_open);
}
}
} // namespace accuracy_tips