blob: 10f63de60527c53f11a4726711a78dccc35960b7 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/hats/trust_safety_sentiment_service.h"
#include <map>
#include <vector>
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/hats/survey_config.h"
#include "chrome/browser/ui/safety_hub/menu_notification_service_factory.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/webui/settings/site_settings_helper.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/webui_url_constants.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/browser/website_settings_registry.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
#include "components/privacy_sandbox/tracking_protection_prefs.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/unified_consent/pref_names.h"
#include "components/version_info/channel.h"
namespace {
base::TimeDelta GetMinTimeToPrompt() {
return base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)
? features::kTrustSafetySentimentSurveyV2MinTimeToPrompt.Get()
: features::kTrustSafetySentimentSurveyMinTimeToPrompt.Get();
}
base::TimeDelta GetMaxTimeToPrompt() {
return base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)
? features::kTrustSafetySentimentSurveyV2MaxTimeToPrompt.Get()
: features::kTrustSafetySentimentSurveyMaxTimeToPrompt.Get();
}
base::TimeDelta GetMinSessionTime() {
DCHECK(base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2));
return features::kTrustSafetySentimentSurveyV2MinSessionTime.Get();
}
int GetRequiredNtpCount() {
return base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)
? base::RandInt(
features::kTrustSafetySentimentSurveyV2NtpVisitsMinRange
.Get(),
features::kTrustSafetySentimentSurveyV2NtpVisitsMaxRange
.Get())
: base::RandInt(
features::kTrustSafetySentimentSurveyNtpVisitsMinRange.Get(),
features::kTrustSafetySentimentSurveyNtpVisitsMaxRange
.Get());
}
int GetMaxRequiredNtpCount() {
return base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)
? features::kTrustSafetySentimentSurveyV2NtpVisitsMaxRange.Get()
: features::kTrustSafetySentimentSurveyNtpVisitsMaxRange.Get();
}
bool HasNonDefaultPrivacySetting(Profile* profile) {
auto* prefs = profile->GetPrefs();
std::vector<std::string> prefs_to_check = {
prefs::kSafeBrowsingEnabled,
prefs::kSafeBrowsingEnhanced,
prefs::kSafeBrowsingScoutReportingEnabled,
prefs::kEnableDoNotTrack,
password_manager::prefs::kPasswordLeakDetectionEnabled,
prefs::kCookieControlsMode,
};
bool has_non_default_pref = false;
for (const auto& pref_name : prefs_to_check) {
auto* pref = prefs->FindPreference(pref_name);
if (!pref->IsDefaultValue() && pref->IsUserControlled()) {
has_non_default_pref = true;
break;
}
}
// Users consenting to sync automatically enable UKM collection
auto* ukm_pref = prefs->FindPreference(
unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled);
auto* sync_consent_pref =
prefs->FindPreference(prefs::kGoogleServicesConsentedToSync);
bool has_non_default_ukm =
ukm_pref->GetValue()->GetBool() !=
sync_consent_pref->GetValue()->GetBool() &&
(ukm_pref->IsUserControlled() || sync_consent_pref->IsUserControlled());
// Check the default value for each user facing content setting. Note that
// this will not include content setting exceptions set via permission
// prompts, as they are site specific.
bool has_non_default_content_setting = false;
auto* map = HostContentSettingsMapFactory::GetForProfile(profile);
for (auto content_setting_type :
site_settings::GetVisiblePermissionCategories()) {
content_settings::ProviderType content_setting_provider;
auto current_value = map->GetDefaultContentSetting(
content_setting_type, &content_setting_provider);
auto content_setting_source =
content_settings::GetSettingSourceFromProviderType(
content_setting_provider);
const bool user_controlled =
content_setting_source == content_settings::SettingSource::kNone ||
content_setting_source == content_settings::SettingSource::kUser;
auto default_value = static_cast<ContentSetting>(
content_settings::WebsiteSettingsRegistry::GetInstance()
->Get(content_setting_type)
->initial_default_value()
.GetInt());
if (current_value != default_value && user_controlled) {
has_non_default_content_setting = true;
break;
}
}
return has_non_default_pref || has_non_default_ukm ||
has_non_default_content_setting;
}
// Generates the Product Specific Data which accompanies survey results for the
// Privacy Settings product area. This includes whether the user is receiving
// the survey because they ran safety check, and whether they have any
// non-default core privacy settings.
std::map<std::string, bool> GetPrivacySettingsProductSpecificData(
Profile* profile,
bool ran_safety_check) {
std::map<std::string, bool> product_specific_data;
product_specific_data["Non default setting"] =
HasNonDefaultPrivacySetting(profile);
product_specific_data["Ran safety check"] = ran_safety_check;
return product_specific_data;
}
// Returns true if the threat_type is not in the phishing, malware, unwanted
// software, or billing threat categories.
bool IsOtherSBInterstitialCategory(safe_browsing::SBThreatType threat_type) {
switch (threat_type) {
case safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_PHISHING:
case safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING:
case safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_MALWARE:
case safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_UNWANTED:
case safe_browsing::SBThreatType::SB_THREAT_TYPE_BILLING:
return false;
default:
return true;
}
}
// Generates the Product Specific Data which accompanies survey results for the
// Password Protection UI product area.
std::map<std::string, bool> BuildProductSpecificDataForPasswordProtection(
Profile* profile,
PasswordProtectionUIType ui_type,
PasswordProtectionUIAction action) {
std::map<std::string, bool> product_specific_data;
product_specific_data["Enhanced protection enabled"] =
safe_browsing::IsEnhancedProtectionEnabled(*profile->GetPrefs());
product_specific_data["Is page info UI"] = false;
product_specific_data["Is modal dialog UI"] = false;
product_specific_data["Is interstitial UI"] = false;
switch (ui_type) {
case PasswordProtectionUIType::PAGE_INFO:
product_specific_data["Is page info UI"] = true;
break;
case PasswordProtectionUIType::MODAL_DIALOG:
product_specific_data["Is modal dialog UI"] = true;
break;
case PasswordProtectionUIType::INTERSTITIAL:
product_specific_data["Is interstitial UI"] = true;
break;
default:
NOTREACHED();
}
product_specific_data["User completed password change"] = false;
product_specific_data["User clicked change password"] = false;
product_specific_data["User ignored warning"] = false;
product_specific_data["User marked as legitimate"] = false;
switch (action) {
case PasswordProtectionUIAction::CHANGE_PASSWORD:
product_specific_data["User clicked change password"] = true;
break;
case PasswordProtectionUIAction::IGNORE_WARNING:
case PasswordProtectionUIAction::CLOSE:
product_specific_data["User ignored warning"] = true;
break;
case PasswordProtectionUIAction::MARK_AS_LEGITIMATE:
product_specific_data["User marked as legitimate"] = true;
break;
default:
NOTREACHED();
}
return product_specific_data;
}
} // namespace
TrustSafetySentimentService::TrustSafetySentimentService(Profile* profile)
: profile_(profile) {
DCHECK(profile);
observed_profiles_.AddObservation(profile);
// As this service is created lazily, there may already be a primary OTR
// profile created for the main profile.
if (auto* primary_otr =
profile->GetPrimaryOTRProfile(/*create_if_needed=*/false)) {
observed_profiles_.AddObservation(primary_otr);
}
if (base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)) {
if (metrics::DesktopSessionDurationTracker::IsInitialized()) {
session_duration_observation_.Observe(
metrics::DesktopSessionDurationTracker::Get());
} else {
CHECK_IS_TEST();
}
performed_control_group_dice_roll_ = false;
}
}
TrustSafetySentimentService::~TrustSafetySentimentService() = default;
void TrustSafetySentimentService::OpenedNewTabPage() {
// Explicit early exit for the common path, where the user has not performed
// any of the trigger actions.
if (pending_triggers_.size() == 0) {
return;
}
// Reduce the NTPs to open count for all the active triggers.
for (auto& area_trigger : pending_triggers_) {
auto& trigger = area_trigger.second;
if (trigger.remaining_ntps_to_open > 0) {
trigger.remaining_ntps_to_open--;
}
}
// Cleanup any triggers which are no longer relevant. This will be every
// trigger which occurred more than the maximum prompt time ago, or the
// trigger for the kIneligible area if it is no longer blocking
// eligibility.
std::erase_if(pending_triggers_,
[](const std::pair<FeatureArea, PendingTrigger>& area_trigger) {
return base::Time::Now() - area_trigger.second.occurred_time >
GetMaxTimeToPrompt() ||
(area_trigger.first == FeatureArea::kIneligible &&
!ShouldBlockSurvey(area_trigger.second));
});
// This may have emptied the set of pending triggers.
if (pending_triggers_.size() == 0) {
return;
}
// A primary OTR profile (incognito) existing will prevent any surveys from
// being shown.
if (profile_->HasPrimaryOTRProfile()) {
return;
}
// Check if any of the triggers make the user not yet eligible to receive a
// survey.
for (const auto& area_trigger : pending_triggers_) {
if (ShouldBlockSurvey(area_trigger.second)) {
return;
}
}
// Choose a trigger at random to avoid any order biasing.
auto winning_area_iterator = pending_triggers_.begin();
std::advance(winning_area_iterator,
base::RandInt(0, pending_triggers_.size() - 1));
// The winning feature area should never be kIneligible, as this will
// have either been removed above, or blocked showing any survey.
DCHECK(winning_area_iterator->first != FeatureArea::kIneligible);
HatsService* hats_service =
HatsServiceFactory::GetForProfile(profile_, /*create_if_necessary=*/true);
// A null HaTS service should have prevented this service from being created.
DCHECK(hats_service);
hats_service->LaunchSurvey(
GetHatsTriggerForFeatureArea(winning_area_iterator->first),
/*success_callback=*/base::DoNothing(),
/*failure_callback=*/base::DoNothing(),
winning_area_iterator->second.product_specific_data);
base::UmaHistogramEnumeration("Feedback.TrustSafetySentiment.SurveyRequested",
winning_area_iterator->first);
pending_triggers_.clear();
}
void TrustSafetySentimentService::InteractedWithPrivacySettings(
content::WebContents* web_contents) {
// Only observe one instance settings at a time. This ignores both multiple
// instances of settings, and repeated interactions with settings. This
// reduces the chance that a user is eligible for a survey, but is much
// simpler. As interactions with settings (visiting password manager and using
// the privacy card) can occur independently, there is also little risk of
// starving one interaction.
if (settings_watcher_) {
return;
}
settings_watcher_ = std::make_unique<SettingsWatcher>(
web_contents,
features::kTrustSafetySentimentSurveyPrivacySettingsTime.Get(),
base::BindOnce(&TrustSafetySentimentService::TriggerOccurred,
weak_ptr_factory_.GetWeakPtr(),
FeatureArea::kPrivacySettings,
GetPrivacySettingsProductSpecificData(
profile_, /*ran_safety_check=*/false)),
base::BindOnce(&TrustSafetySentimentService::SettingsWatcherComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void TrustSafetySentimentService::RanSafetyCheck() {
// Since we have logic to block a trigger for an incorrect version, we can
// call both of these and only the appropriate trigger and probability will be
// recorded.
TriggerOccurred(FeatureArea::kSafetyCheck, {});
TriggerOccurred(FeatureArea::kPrivacySettings,
GetPrivacySettingsProductSpecificData(
profile_, /*ran_safety_check=*/true));
}
void TrustSafetySentimentService::PageInfoOpened() {
// Only one Page Info should ever be open.
DCHECK(!page_info_state_);
page_info_state_ = std::make_unique<PageInfoState>();
}
void TrustSafetySentimentService::InteractedWithPageInfo() {
DCHECK(page_info_state_);
page_info_state_->interacted = true;
}
void TrustSafetySentimentService::PageInfoClosed() {
if (!page_info_state_) {
return;
}
base::TimeDelta threshold =
base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)
? features::kTrustSafetySentimentSurveyV2TrustedSurfaceTime.Get()
: features::kTrustSafetySentimentSurveyTrustedSurfaceTime.Get();
// Record a trigger if either the user had page info open for the required
// time, or if they interacted with it.
if (base::Time::Now() - page_info_state_->opened_time >= threshold ||
page_info_state_->interacted) {
TriggerOccurred(
FeatureArea::kTrustedSurface,
{{"Interacted with Page Info", page_info_state_->interacted}});
}
page_info_state_.reset();
}
void TrustSafetySentimentService::SavedPassword() {
TriggerOccurred(FeatureArea::kTransactions, {{"Saved password", true}});
}
void TrustSafetySentimentService::OpenedPasswordManager(
content::WebContents* web_contents) {
if (settings_watcher_) {
return;
}
std::map<std::string, bool> product_specific_data = {
{"Saved password", false}};
settings_watcher_ = std::make_unique<SettingsWatcher>(
web_contents,
features::kTrustSafetySentimentSurveyTransactionsPasswordManagerTime
.Get(),
base::BindOnce(&TrustSafetySentimentService::TriggerOccurred,
weak_ptr_factory_.GetWeakPtr(), FeatureArea::kTransactions,
product_specific_data),
base::BindOnce(&TrustSafetySentimentService::SettingsWatcherComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void TrustSafetySentimentService::SavedCard() {
TriggerOccurred(FeatureArea::kTransactions, {{"Saved password", false}});
}
void TrustSafetySentimentService::RanPasswordCheck() {
TriggerOccurred(FeatureArea::kPasswordCheck, {});
}
void TrustSafetySentimentService::ClearedBrowsingData(
browsing_data::BrowsingDataType datatype) {
// We are only interested in history, downloads, and autofill.
switch (datatype) {
case (browsing_data::BrowsingDataType::HISTORY):
case (browsing_data::BrowsingDataType::DOWNLOADS):
case (browsing_data::BrowsingDataType::FORM_DATA):
break;
default:
return;
}
return TriggerOccurred(
FeatureArea::kBrowsingData,
{{"Deleted history",
datatype == browsing_data::BrowsingDataType::HISTORY},
{"Deleted downloads",
datatype == browsing_data::BrowsingDataType::DOWNLOADS},
{"Deleted autofill form data",
datatype == browsing_data::BrowsingDataType::FORM_DATA}});
;
}
void TrustSafetySentimentService::FinishedPrivacyGuide() {
TriggerOccurred(FeatureArea::kPrivacyGuide, {});
}
void TrustSafetySentimentService::InteractedWithSafeBrowsingInterstitial(
bool did_proceed,
safe_browsing::SBThreatType threat_type) {
std::map<std::string, bool> product_specific_data;
product_specific_data["User proceeded past interstitial"] = did_proceed;
product_specific_data["Enhanced protection enabled"] =
safe_browsing::IsEnhancedProtectionEnabled(*profile_->GetPrefs());
product_specific_data["Threat is phishing"] =
threat_type == safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_PHISHING ||
threat_type ==
safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING;
product_specific_data["Threat is malware"] =
threat_type == safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_MALWARE;
product_specific_data["Threat is unwanted software"] =
threat_type == safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_UNWANTED;
product_specific_data["Threat is billing"] =
threat_type == safe_browsing::SBThreatType::SB_THREAT_TYPE_BILLING;
DCHECK(!IsOtherSBInterstitialCategory(threat_type));
TriggerOccurred(FeatureArea::kSafeBrowsingInterstitial,
product_specific_data);
}
void TrustSafetySentimentService::InteractedWithDownloadWarningUI(
DownloadItemWarningData::WarningSurface surface,
DownloadItemWarningData::WarningAction action) {
std::map<std::string, bool> product_specific_data;
product_specific_data["Enhanced protection enabled"] =
safe_browsing::IsEnhancedProtectionEnabled(*profile_->GetPrefs());
product_specific_data["Is mainpage UI"] = false;
product_specific_data["Is downloads page UI"] = false;
product_specific_data["Is download prompt UI"] = false;
product_specific_data["User proceeded past warning"] = false;
product_specific_data["Is subpage UI"] = false;
switch (surface) {
case DownloadItemWarningData::WarningSurface::BUBBLE_MAINPAGE:
product_specific_data["Is mainpage UI"] = true;
break;
case DownloadItemWarningData::WarningSurface::BUBBLE_SUBPAGE:
product_specific_data["Is subpage UI"] = true;
break;
case DownloadItemWarningData::WarningSurface::DOWNLOADS_PAGE:
product_specific_data["Is downloads page UI"] = true;
break;
case DownloadItemWarningData::WarningSurface::DOWNLOAD_PROMPT:
product_specific_data["Is download prompt UI"] = true;
break;
default:
NOTREACHED();
}
switch (action) {
case DownloadItemWarningData::WarningAction::PROCEED:
product_specific_data["User proceeded past warning"] = true;
break;
case DownloadItemWarningData::WarningAction::DISCARD:
product_specific_data["User proceeded past warning"] = false;
break;
default:
NOTREACHED();
}
TriggerOccurred(FeatureArea::kDownloadWarningUI, product_specific_data);
}
void TrustSafetySentimentService::ProtectResetOrCheckPasswordClicked(
PasswordProtectionUIType ui_type) {
// Only one Phished Password Change should ever be open.
DCHECK(!phished_password_change_state_);
phished_password_change_state_ =
std::make_unique<PhishedPasswordChangeState>();
phished_password_change_state_->ui_type_ = ui_type;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&TrustSafetySentimentService::MaybeTriggerPasswordProtectionSurvey,
weak_ptr_factory_.GetWeakPtr(), ui_type,
PasswordProtectionUIAction::CHANGE_PASSWORD),
kPasswordChangeInactivity);
}
void TrustSafetySentimentService::PhishedPasswordUpdateNotClicked(
PasswordProtectionUIType ui_type,
PasswordProtectionUIAction action) {
DCHECK(action != PasswordProtectionUIAction::CHANGE_PASSWORD);
MaybeTriggerPasswordProtectionSurvey(ui_type, action);
}
void TrustSafetySentimentService::PhishedPasswordUpdateFinished() {
if (!phished_password_change_state_) {
return;
}
phished_password_change_state_->finished_action = true;
MaybeTriggerPasswordProtectionSurvey(
phished_password_change_state_->ui_type_,
PasswordProtectionUIAction::CHANGE_PASSWORD);
}
void TrustSafetySentimentService::OnOffTheRecordProfileCreated(
Profile* off_the_record) {
// Only interested in the primary OTR profile i.e. the one used for incognito
// browsing. Non-primary OTR profiles are often used as implementation details
// of other features, and are not inherintly relevant to Trust & Safety.
if (off_the_record->GetOTRProfileID() == Profile::OTRProfileID::PrimaryID()) {
observed_profiles_.AddObservation(off_the_record);
}
}
void TrustSafetySentimentService::OnProfileWillBeDestroyed(Profile* profile) {
observed_profiles_.RemoveObservation(profile);
if (profile->IsOffTheRecord()) {
// Closing the incognito profile, which is the only OTR profie observed by
// this class, is an ileligible action.
PerformedIneligibleAction();
}
}
void TrustSafetySentimentService::OnSessionEnded(base::TimeDelta session_length,
base::TimeTicks session_end) {
DCHECK(base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2));
// Check if the user is eligible for the control group.
if (!performed_control_group_dice_roll_ &&
session_length > GetMinSessionTime()) {
performed_control_group_dice_roll_ = true;
TriggerOccurred(FeatureArea::kControlGroup, {});
}
}
TrustSafetySentimentService::PendingTrigger::PendingTrigger(
const std::map<std::string, bool>& product_specific_data,
int remaining_ntps_to_open)
: product_specific_data(product_specific_data),
remaining_ntps_to_open(remaining_ntps_to_open),
occurred_time(base::Time::Now()) {}
TrustSafetySentimentService::PendingTrigger::PendingTrigger(
int remaining_ntps_to_open)
: remaining_ntps_to_open(remaining_ntps_to_open),
occurred_time(base::Time::Now()) {}
TrustSafetySentimentService::PendingTrigger::PendingTrigger() = default;
TrustSafetySentimentService::PendingTrigger::~PendingTrigger() = default;
TrustSafetySentimentService::PendingTrigger::PendingTrigger(
const PendingTrigger& other) = default;
TrustSafetySentimentService::SettingsWatcher::SettingsWatcher(
content::WebContents* web_contents,
base::TimeDelta required_open_time,
base::OnceCallback<void()> success_callback,
base::OnceCallback<void()> complete_callback)
: web_contents_(web_contents),
success_callback_(std::move(success_callback)),
complete_callback_(std::move(complete_callback)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&TrustSafetySentimentService::SettingsWatcher::TimerComplete,
weak_ptr_factory_.GetWeakPtr()),
required_open_time);
Observe(web_contents);
}
TrustSafetySentimentService::SettingsWatcher::~SettingsWatcher() = default;
void TrustSafetySentimentService::SettingsWatcher::WebContentsDestroyed() {
std::move(complete_callback_).Run();
}
void TrustSafetySentimentService::SettingsWatcher::TimerComplete() {
const bool stayed_on_settings =
web_contents_ &&
web_contents_->GetVisibility() == content::Visibility::VISIBLE &&
web_contents_->GetLastCommittedURL().host() ==
chrome::kChromeUISettingsHost;
if (stayed_on_settings) {
std::move(success_callback_).Run();
}
std::move(complete_callback_).Run();
}
TrustSafetySentimentService::PageInfoState::PageInfoState()
: opened_time(base::Time::Now()) {}
TrustSafetySentimentService::PhishedPasswordChangeState::
PhishedPasswordChangeState()
: password_change_click_ts_(base::Time::Now()),
ui_type_(PasswordProtectionUIType::NOT_USED) {}
void TrustSafetySentimentService::SettingsWatcherComplete() {
settings_watcher_.reset();
}
void TrustSafetySentimentService::TriggerOccurred(
FeatureArea feature_area,
const std::map<std::string, bool>& product_specific_data) {
// Log histogram that verifies infrastructure works as intended.
base::UmaHistogramEnumeration(
"Feedback.TrustSafetySentiment.CallTriggerOccurred", feature_area);
if (!ProbabilityCheck(feature_area)) {
return;
}
base::UmaHistogramEnumeration("Feedback.TrustSafetySentiment.TriggerOccurred",
feature_area);
// This will overwrite any previous trigger for this feature area. We are
// only interested in the most recent trigger, so this is acceptable.
pending_triggers_[feature_area] =
PendingTrigger(product_specific_data, GetRequiredNtpCount());
}
void TrustSafetySentimentService::PerformedIneligibleAction() {
pending_triggers_[FeatureArea::kIneligible] =
PendingTrigger(GetMaxRequiredNtpCount());
base::UmaHistogramEnumeration("Feedback.TrustSafetySentiment.TriggerOccurred",
FeatureArea::kIneligible);
}
/*static*/ bool TrustSafetySentimentService::ShouldBlockSurvey(
const PendingTrigger& trigger) {
return base::Time::Now() - trigger.occurred_time < GetMinTimeToPrompt() ||
trigger.remaining_ntps_to_open > 0;
}
// Checks inactivity delay and finished_action (change psd field to true)
void TrustSafetySentimentService::MaybeTriggerPasswordProtectionSurvey(
PasswordProtectionUIType ui_type,
PasswordProtectionUIAction action) {
DCHECK(ui_type != PasswordProtectionUIType::NOT_USED);
std::map<std::string, bool> product_specific_data =
BuildProductSpecificDataForPasswordProtection(profile_, ui_type, action);
if (action == PasswordProtectionUIAction::CHANGE_PASSWORD) {
if (!phished_password_change_state_) {
return;
}
if (!phished_password_change_state_->finished_action &&
base::Time::Now() -
phished_password_change_state_->password_change_click_ts_ <
kPasswordChangeInactivity) {
return;
}
if (phished_password_change_state_->finished_action) {
product_specific_data["User completed password change"] = true;
}
phished_password_change_state_.reset();
}
TriggerOccurred(FeatureArea::kPasswordProtectionUI, product_specific_data);
}
void TrustSafetySentimentService::TriggerSafetyHubSurvey(
TrustSafetySentimentService::FeatureArea feature_area,
std::map<std::string, bool> product_specific_data) {
if (!base::FeatureList::IsEnabled(
features::kSafetyHubTrustSafetySentimentSurvey)) {
return;
}
// Delay the trigger to determine whether the user interacted with Safety Hub
// soon after the trigger occurred.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&TrustSafetySentimentService::TriggerOccurred,
weak_ptr_factory_.GetWeakPtr(), feature_area,
product_specific_data),
kSafetyHubSurveyDelay);
}
// static
bool TrustSafetySentimentService::VersionCheck(FeatureArea feature_area) {
bool isV2 =
base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2);
switch (feature_area) {
// Version 1 only
case (FeatureArea::kPrivacySettings):
case (FeatureArea::kTransactions):
return isV2 == false;
// Version 2 only
case (FeatureArea::kSafetyCheck):
case (FeatureArea::kSafetyHubInteracted):
case (FeatureArea::kSafetyHubNotification):
case (FeatureArea::kPasswordCheck):
case (FeatureArea::kBrowsingData):
case (FeatureArea::kPrivacyGuide):
case (FeatureArea::kControlGroup):
case (FeatureArea::kSafeBrowsingInterstitial):
case (FeatureArea::kDownloadWarningUI):
case (FeatureArea::kPasswordProtectionUI):
return isV2 == true;
// Both Versions
case (FeatureArea::kTrustedSurface):
return true;
// None
case (FeatureArea::kIneligible):
return false;
default:
NOTREACHED();
}
}
// static
std::string TrustSafetySentimentService::GetHatsTriggerForFeatureArea(
FeatureArea feature_area) {
if (base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)) {
switch (feature_area) {
case (FeatureArea::kTrustedSurface):
return kHatsSurveyTriggerTrustSafetyV2TrustedSurface;
case (FeatureArea::kSafetyCheck):
return kHatsSurveyTriggerTrustSafetyV2SafetyCheck;
case (FeatureArea::kSafetyHubInteracted):
return kHatsSurveyTriggerTrustSafetyV2SafetyHubInteraction;
case (FeatureArea::kSafetyHubNotification):
return kHatsSurveyTriggerTrustSafetyV2SafetyHubNotification;
case (FeatureArea::kPasswordCheck):
return kHatsSurveyTriggerTrustSafetyV2PasswordCheck;
case (FeatureArea::kBrowsingData):
return kHatsSurveyTriggerTrustSafetyV2BrowsingData;
case (FeatureArea::kPrivacyGuide):
return kHatsSurveyTriggerTrustSafetyV2PrivacyGuide;
case (FeatureArea::kControlGroup):
return kHatsSurveyTriggerTrustSafetyV2ControlGroup;
case (FeatureArea::kSafeBrowsingInterstitial):
return kHatsSurveyTriggerTrustSafetyV2SafeBrowsingInterstitial;
case (FeatureArea::kDownloadWarningUI):
return kHatsSurveyTriggerTrustSafetyV2DownloadWarningUI;
case (FeatureArea::kPasswordProtectionUI):
return kHatsSurveyTriggerTrustSafetyV2PasswordProtectionUI;
default:
NOTREACHED();
}
}
switch (feature_area) {
case (FeatureArea::kPrivacySettings):
return kHatsSurveyTriggerTrustSafetyPrivacySettings;
case (FeatureArea::kTrustedSurface):
return kHatsSurveyTriggerTrustSafetyTrustedSurface;
case (FeatureArea::kTransactions):
return kHatsSurveyTriggerTrustSafetyTransactions;
default:
NOTREACHED();
}
}
// static
bool TrustSafetySentimentService::ProbabilityCheck(FeatureArea feature_area) {
if (!TrustSafetySentimentService::VersionCheck(feature_area)) {
return false;
}
if (base::FeatureList::IsEnabled(features::kTrustSafetySentimentSurveyV2)) {
switch (feature_area) {
case (FeatureArea::kTrustedSurface):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyV2TrustedSurfaceProbability
.Get();
case (FeatureArea::kSafetyCheck):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyV2SafetyCheckProbability
.Get();
case (FeatureArea::kSafetyHubInteracted):
return base::RandDouble() <
features::
kTrustSafetySentimentSurveyV2SafetyHubInteractionProbability
.Get();
case (FeatureArea::kSafetyHubNotification):
return base::RandDouble() <
features::
kTrustSafetySentimentSurveyV2SafetyHubNotificationProbability
.Get();
case (FeatureArea::kPasswordCheck):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyV2PasswordCheckProbability
.Get();
case (FeatureArea::kBrowsingData):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyV2BrowsingDataProbability
.Get();
case (FeatureArea::kPrivacyGuide):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyV2PrivacyGuideProbability
.Get();
case (FeatureArea::kControlGroup):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyV2ControlGroupProbability
.Get();
case (FeatureArea::kSafeBrowsingInterstitial):
return base::RandDouble() <
features::
kTrustSafetySentimentSurveyV2SafeBrowsingInterstitialProbability
.Get();
case (FeatureArea::kDownloadWarningUI):
return base::RandDouble() <
features::
kTrustSafetySentimentSurveyV2DownloadWarningUIProbability
.Get();
case (FeatureArea::kPasswordProtectionUI):
return base::RandDouble() <
features::
kTrustSafetySentimentSurveyV2PasswordProtectionUIProbability
.Get();
default:
NOTREACHED();
}
}
switch (feature_area) {
case (FeatureArea::kPrivacySettings):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyPrivacySettingsProbability
.Get();
case (FeatureArea::kTrustedSurface):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyTrustedSurfaceProbability
.Get();
case (FeatureArea::kTransactions):
return base::RandDouble() <
features::kTrustSafetySentimentSurveyTransactionsProbability.Get();
default:
NOTREACHED();
}
}