blob: 231471293bcd96aceac406390da01e48cce5bfae [file] [log] [blame]
// Copyright 2019 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/permissions/adaptive_quiet_notification_permission_ui_enabler.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/adapters.h"
#include "base/json/values_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_actions_history_factory.h"
#include "chrome/browser/permissions/quiet_notification_permission_ui_config.h"
#include "chrome/browser/permissions/quiet_notification_permission_ui_state.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/permissions/permission_actions_history.h"
#include "components/permissions/permission_request_enums.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/request_type.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/scoped_user_pref_update.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/profiles/profile_helper.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace {
using EnablingMethod = QuietNotificationPermissionUiState::EnablingMethod;
// Enable the quiet UX after 3 consecutive denies in adapative activation mode.
constexpr int kConsecutiveDeniesThresholdForActivation = 3u;
constexpr char kDidAdaptivelyEnableQuietUiInPrefs[] =
"Permissions.QuietNotificationPrompts.DidEnableAdapativelyInPrefs";
constexpr char kIsQuietUiEnabledInPrefs[] =
"Permissions.QuietNotificationPrompts.RegularProfile.IsEnabledInPrefs";
constexpr char kQuietUiEnabledStateInPrefsChangedTo[] =
"Permissions.QuietNotificationPrompts.EnabledStateInPrefsChangedTo";
bool DidDenyLastThreeTimes(
const std::vector<permissions::PermissionActionsHistory::Entry>&
permission_actions) {
size_t rolling_denies_in_a_row = 0u;
for (const auto& entry : base::Reversed(permission_actions)) {
switch (entry.action) {
case permissions::PermissionAction::DENIED:
++rolling_denies_in_a_row;
break;
case permissions::PermissionAction::GRANTED:
return false; // Does not satisfy adaptive quiet UI activation
// condition.
case permissions::PermissionAction::DISMISSED:
case permissions::PermissionAction::IGNORED:
case permissions::PermissionAction::REVOKED:
default:
// Ignored.
break;
}
if (rolling_denies_in_a_row >= kConsecutiveDeniesThresholdForActivation) {
return true;
}
}
return false;
}
} // namespace
// AdaptiveQuietNotificationPermissionUiEnabler::Factory ---------------------
// static
AdaptiveQuietNotificationPermissionUiEnabler*
AdaptiveQuietNotificationPermissionUiEnabler::Factory::GetForProfile(
Profile* profile) {
return static_cast<AdaptiveQuietNotificationPermissionUiEnabler*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
// static
AdaptiveQuietNotificationPermissionUiEnabler::Factory*
AdaptiveQuietNotificationPermissionUiEnabler::Factory::GetInstance() {
return base::Singleton<
AdaptiveQuietNotificationPermissionUiEnabler::Factory>::get();
}
AdaptiveQuietNotificationPermissionUiEnabler::Factory::Factory()
: ProfileKeyedServiceFactory(
"AdaptiveQuietNotificationPermissionUiEnabler",
ProfileSelections::BuildRedirectedInIncognito()) {
DependsOn(HostContentSettingsMapFactory::GetInstance());
}
AdaptiveQuietNotificationPermissionUiEnabler::Factory::~Factory() {}
KeyedService*
AdaptiveQuietNotificationPermissionUiEnabler::Factory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new AdaptiveQuietNotificationPermissionUiEnabler(
static_cast<Profile*>(context));
}
// AdaptiveQuietNotificationPermissionUiEnabler ------------------------------
// static
AdaptiveQuietNotificationPermissionUiEnabler*
AdaptiveQuietNotificationPermissionUiEnabler::GetForProfile(Profile* profile) {
return AdaptiveQuietNotificationPermissionUiEnabler::Factory::GetForProfile(
profile);
}
void AdaptiveQuietNotificationPermissionUiEnabler::PermissionPromptResolved() {
if ((!QuietNotificationPermissionUiConfig::
IsAdaptiveActivationDryRunEnabled() ||
profile_->GetPrefs()->GetBoolean(
prefs::kHadThreeConsecutiveNotificationPermissionDenies)) &&
(!QuietNotificationPermissionUiConfig::IsAdaptiveActivationEnabled() ||
profile_->GetPrefs()->GetBoolean(
prefs::kEnableQuietNotificationPermissionUi))) {
return;
}
// If the user has disabled the quiet UI, only count events after that
// occurred.
const base::Time disable_time = profile_->GetPrefs()->GetTime(
prefs::kQuietNotificationPermissionUiDisabledTime);
// Limit how old actions are to be taken into consideration. Default 90 days
// old. The value could be changed based on Finch experiment but specifying >
// 90 days is meaningless, as only 90 days worth of history is stored.
const base::Time cutoff =
base::Time::Now() -
QuietNotificationPermissionUiConfig::GetAdaptiveActivationWindowSize();
const auto actions =
PermissionActionsHistoryFactory::GetForProfile(profile_)->GetHistory(
std::max(cutoff, disable_time),
permissions::RequestType::kNotifications,
permissions::PermissionActionsHistory::EntryFilter::WANT_ALL_PROMPTS);
if (!DidDenyLastThreeTimes(actions)) {
return;
}
if (QuietNotificationPermissionUiConfig::
IsAdaptiveActivationDryRunEnabled()) {
profile_->GetPrefs()->SetBoolean(
prefs::kHadThreeConsecutiveNotificationPermissionDenies,
true /* value */);
}
if (QuietNotificationPermissionUiConfig::IsAdaptiveActivationEnabled() &&
!profile_->GetPrefs()->GetBoolean(
prefs::kEnableQuietNotificationPermissionUi)) {
// Set |is_enabling_adaptively_| for the duration of the pref update to
// inform OnQuietUiStateChanged() that the quiet UI is being enabled
// adaptively, so that it can record the correct metrics.
base::AutoReset<bool> enabling_adaptively(&is_enabling_adaptively_, true);
profile_->GetPrefs()->SetBoolean(
prefs::kEnableQuietNotificationPermissionUi, true /* value */);
// TODO(crbug.com/1147467): If `kQuietNotificationPermissionShouldShowPromo`
// stops being a good indicator as to how the quiet UI pref was enabled,
// remove the |BackfillEnablingMethodIfMissing| logic.
profile_->GetPrefs()->SetBoolean(
prefs::kQuietNotificationPermissionShouldShowPromo, true /* value */);
}
}
AdaptiveQuietNotificationPermissionUiEnabler::
AdaptiveQuietNotificationPermissionUiEnabler(Profile* profile)
: profile_(profile) {
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(profile_->GetPrefs());
pref_change_registrar_->Add(
prefs::kEnableQuietNotificationPermissionUi,
base::BindRepeating(
&AdaptiveQuietNotificationPermissionUiEnabler::OnQuietUiStateChanged,
base::Unretained(this)));
bool should_record_metrics = profile_->IsRegularProfile();
#if BUILDFLAG(IS_CHROMEOS_ASH)
// ChromeOS creates various irregular profiles (login, lock screen...); they
// are of type kRegular (returns true for `Profile::IsRegular()`), that aren't
// used to browse the web and users can't configure. Don't collect metrics
// about them.
should_record_metrics =
should_record_metrics && ash::ProfileHelper::IsUserProfile(profile_);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
if (should_record_metrics) {
// Record whether the quiet UI is enabled, but only when notifications are
// not completely blocked.
auto* host_content_settings_map =
HostContentSettingsMapFactory::GetForProfile(profile_);
const ContentSetting notifications_setting =
host_content_settings_map->GetDefaultContentSetting(
ContentSettingsType::NOTIFICATIONS, nullptr /* provider_id */);
if (notifications_setting != CONTENT_SETTING_BLOCK) {
const bool is_quiet_ui_enabled_in_prefs =
profile_->GetPrefs()->GetBoolean(
prefs::kEnableQuietNotificationPermissionUi);
base::UmaHistogramBoolean(kIsQuietUiEnabledInPrefs,
is_quiet_ui_enabled_in_prefs);
}
}
BackfillEnablingMethodIfMissing();
}
AdaptiveQuietNotificationPermissionUiEnabler::
~AdaptiveQuietNotificationPermissionUiEnabler() = default;
void AdaptiveQuietNotificationPermissionUiEnabler::OnQuietUiStateChanged() {
const bool is_quiet_ui_enabled_in_prefs = profile_->GetPrefs()->GetBoolean(
prefs::kEnableQuietNotificationPermissionUi);
base::UmaHistogramBoolean(kQuietUiEnabledStateInPrefsChangedTo,
is_quiet_ui_enabled_in_prefs);
if (is_quiet_ui_enabled_in_prefs) {
base::UmaHistogramBoolean(kDidAdaptivelyEnableQuietUiInPrefs,
is_enabling_adaptively_);
profile_->GetPrefs()->SetInteger(
prefs::kQuietNotificationPermissionUiEnablingMethod,
static_cast<int>(is_enabling_adaptively_ ? EnablingMethod::kAdaptive
: EnablingMethod::kManual));
} else {
// Reset the promo state so that if the quiet UI is enabled adaptively
// again, the promo will be shown again.
profile_->GetPrefs()->ClearPref(
prefs::kQuietNotificationPermissionShouldShowPromo);
profile_->GetPrefs()->ClearPref(
prefs::kQuietNotificationPermissionPromoWasShown);
profile_->GetPrefs()->ClearPref(
prefs::kQuietNotificationPermissionUiEnablingMethod);
// If the users has just turned off the quiet UI remember the time when it
// happened. Only actions taking place after this point will be considered
// from now on.
profile_->GetPrefs()->SetTime(
prefs::kQuietNotificationPermissionUiDisabledTime, base::Time::Now());
}
}
void AdaptiveQuietNotificationPermissionUiEnabler::
BackfillEnablingMethodIfMissing() {
if (QuietNotificationPermissionUiState::GetQuietUiEnablingMethod(profile_) !=
EnablingMethod::kUnspecified) {
return;
}
// `kQuietNotificationPermissionUiEnablingMethod` was not populated prior to
// M88, but `kQuietNotificationPermissionShouldShowPromo` is a solid
// indicator as to how the setting was enabled in the first place because
// it's only set to true when the quiet UI has been enabled adaptively.
const bool has_enabled_adaptively = profile_->GetPrefs()->GetBoolean(
prefs::kQuietNotificationPermissionShouldShowPromo);
profile_->GetPrefs()->SetInteger(
prefs::kQuietNotificationPermissionUiEnablingMethod,
static_cast<int>(has_enabled_adaptively ? EnablingMethod::kAdaptive
: EnablingMethod::kManual));
}