blob: e4a76b5efad6f0c9bb4bf87639844d64bf4291eb [file] [log] [blame]
// Copyright 2018 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/unified_consent/unified_consent_service.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/scoped_observer.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/contextual_search/core/browser/contextual_search_preference.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/sync/base/model_type.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/unified_consent/feature.h"
#include "components/unified_consent/pref_names.h"
namespace unified_consent {
namespace {
// Used for observing the sync service and finishing the rollback once the sync
// engine is initialized.
// Note: This object is suicidal - it will kill itself after it finishes the
// rollback.
class RollbackHelper : public syncer::SyncServiceObserver {
public:
explicit RollbackHelper(syncer::SyncService* sync_service);
~RollbackHelper() override = default;
private:
// syncer::SyncServiceObserver:
void OnStateChanged(syncer::SyncService* sync_service) override;
void DoRollbackIfPossibleAndDie(syncer::SyncService* sync_service);
ScopedObserver<syncer::SyncService, RollbackHelper> scoped_sync_observer_;
};
RollbackHelper::RollbackHelper(syncer::SyncService* sync_service)
: scoped_sync_observer_(this) {
if (sync_service->IsEngineInitialized())
DoRollbackIfPossibleAndDie(sync_service);
else
scoped_sync_observer_.Add(sync_service);
}
void RollbackHelper::OnStateChanged(syncer::SyncService* sync_service) {
if (!sync_service->IsEngineInitialized())
return;
scoped_sync_observer_.RemoveAll();
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&RollbackHelper::DoRollbackIfPossibleAndDie,
base::Unretained(this), sync_service));
}
void RollbackHelper::DoRollbackIfPossibleAndDie(
syncer::SyncService* sync_service) {
DCHECK(!scoped_sync_observer_.IsObservingSources());
// Warning: ugly code ahead. See https://crbug.com/885382 for background.
#if defined(OS_ANDROID) || defined(OS_IOS)
syncer::ModelTypeSet user_selectable_types_except_user_events(
syncer::AUTOFILL, syncer::BOOKMARKS, syncer::PASSWORDS,
syncer::PREFERENCES, syncer::PROXY_TABS,
#if BUILDFLAG(ENABLE_READING_LIST)
syncer::READING_LIST,
#endif
syncer::TYPED_URLS);
#else
syncer::ModelTypeSet user_selectable_types_except_user_events =
syncer::UserSelectableTypes();
// USER_EVENTS data type doesn't have to be enabled, because it is not
// configurable if Unified Consent feature is disabled.
user_selectable_types_except_user_events.Remove(syncer::USER_EVENTS);
#endif
if (sync_service->GetUserSettings()->GetChosenDataTypes().HasAll(
user_selectable_types_except_user_events)) {
// As part of the migration of a profile to Unified Consent, sync everything
// is disabled but sync continues to be enabled for all data types except
// USER_EVENTS. Therefore it is desired to restore sync everything when
// rolling back unified consent to leave sync in the same state as the one
// before migration.
sync_service->GetUserSettings()->SetChosenDataTypes(
/*sync_everything=*/true, syncer::UserSelectableTypes());
}
base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
} // namespace
UnifiedConsentService::UnifiedConsentService(
std::unique_ptr<UnifiedConsentServiceClient> service_client,
PrefService* pref_service,
identity::IdentityManager* identity_manager,
syncer::SyncService* sync_service)
: service_client_(std::move(service_client)),
pref_service_(pref_service),
identity_manager_(identity_manager),
sync_service_(sync_service),
weak_ptr_factory_(this) {
DCHECK(service_client_);
DCHECK(pref_service_);
DCHECK(identity_manager_);
DCHECK(sync_service_);
if (GetMigrationState() == MigrationState::kNotInitialized)
MigrateProfileToUnifiedConsent();
// Check if this profile is still eligible for the consent bump.
CheckConsentBumpEligibility();
service_client_->AddObserver(this);
identity_manager_->AddObserver(this);
sync_service_->AddObserver(this);
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(pref_service_);
pref_change_registrar_->Add(
prefs::kUnifiedConsentGiven,
base::BindRepeating(
&UnifiedConsentService::OnUnifiedConsentGivenPrefChanged,
base::Unretained(this)));
// If somebody disabled any of the non-personalized services while Chrome
// wasn't running, disable unified consent.
if (!AreAllNonPersonalizedServicesEnabled() && IsUnifiedConsentGiven()) {
SetUnifiedConsentGiven(false);
RecordUnifiedConsentRevoked(
metrics::UnifiedConsentRevokeReason::kServiceWasDisabled);
}
// Update pref for existing users.
// TODO(tangltom): Delete this when all users are migrated.
if (pref_service_->GetBoolean(prefs::kUnifiedConsentGiven))
pref_service_->SetBoolean(prefs::kAllUnifiedConsentServicesWereEnabled,
true);
RecordSettingsHistogram();
}
UnifiedConsentService::~UnifiedConsentService() {}
// static
void UnifiedConsentService::RegisterPrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
false);
registry->RegisterBooleanPref(prefs::kUnifiedConsentGiven, false);
registry->RegisterIntegerPref(
prefs::kUnifiedConsentMigrationState,
static_cast<int>(MigrationState::kNotInitialized));
registry->RegisterBooleanPref(prefs::kShouldShowUnifiedConsentBump, false);
registry->RegisterBooleanPref(prefs::kHadEverythingSyncedBeforeMigration,
false);
registry->RegisterBooleanPref(prefs::kAllUnifiedConsentServicesWereEnabled,
false);
}
// static
void UnifiedConsentService::RollbackIfNeeded(
PrefService* user_pref_service,
syncer::SyncService* sync_service,
UnifiedConsentServiceClient* service_client) {
DCHECK(user_pref_service);
DCHECK(service_client);
if (user_pref_service->GetInteger(prefs::kUnifiedConsentMigrationState) ==
static_cast<int>(MigrationState::kNotInitialized)) {
// If there was no migration yet, nothing has to be rolled back.
return;
}
bool had_everything_synced =
user_pref_service->GetBoolean(
prefs::kHadEverythingSyncedBeforeMigration) ||
user_pref_service->GetBoolean(prefs::kShouldShowUnifiedConsentBump);
if (had_everything_synced && sync_service &&
sync_service->GetDisableReasons() ==
syncer::SyncService::DISABLE_REASON_NONE) {
// This will wait until the sync engine is initialized and then enables the
// sync-everything pref in case the user is syncing all data types.
new RollbackHelper(sync_service);
}
// Turn off all off-by-default services if services were enabled due to
// unified consent.
if (user_pref_service->GetBoolean(
prefs::kAllUnifiedConsentServicesWereEnabled)) {
service_client->SetServiceEnabled(Service::kSafeBrowsingExtendedReporting,
false);
service_client->SetServiceEnabled(Service::kSpellCheck, false);
contextual_search::ContextualSearchPreference::GetInstance()->SetPref(
user_pref_service, false);
}
// Clear all unified consent prefs.
user_pref_service->ClearPref(prefs::kUrlKeyedAnonymizedDataCollectionEnabled);
user_pref_service->ClearPref(prefs::kUnifiedConsentGiven);
user_pref_service->ClearPref(prefs::kUnifiedConsentMigrationState);
user_pref_service->ClearPref(prefs::kShouldShowUnifiedConsentBump);
user_pref_service->ClearPref(prefs::kHadEverythingSyncedBeforeMigration);
user_pref_service->ClearPref(prefs::kAllUnifiedConsentServicesWereEnabled);
}
void UnifiedConsentService::SetUnifiedConsentGiven(bool unified_consent_given) {
// Unified consent cannot be enabled if the user is not signed in.
DCHECK(!unified_consent_given || identity_manager_->HasPrimaryAccount());
pref_service_->SetBoolean(prefs::kUnifiedConsentGiven, unified_consent_given);
}
bool UnifiedConsentService::IsUnifiedConsentGiven() {
return pref_service_->GetBoolean(prefs::kUnifiedConsentGiven);
}
bool UnifiedConsentService::ShouldShowConsentBump() {
if (base::FeatureList::IsEnabled(kForceUnifiedConsentBump) &&
identity_manager_->HasPrimaryAccount()) {
return true;
}
return pref_service_->GetBoolean(prefs::kShouldShowUnifiedConsentBump);
}
void UnifiedConsentService::MarkConsentBumpShown() {
// Record suppress reason kNone, which means that it was shown. This also sets
// the |kShouldShowConsentBump| pref to false.
RecordConsentBumpSuppressReason(metrics::ConsentBumpSuppressReason::kNone);
}
void UnifiedConsentService::RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason suppress_reason) {
UMA_HISTOGRAM_ENUMERATION("UnifiedConsent.ConsentBump.SuppressReason",
suppress_reason);
switch (suppress_reason) {
case metrics::ConsentBumpSuppressReason::kNone:
case metrics::ConsentBumpSuppressReason::kNotSignedIn:
case metrics::ConsentBumpSuppressReason::kSyncEverythingOff:
case metrics::ConsentBumpSuppressReason::kPrivacySettingOff:
case metrics::ConsentBumpSuppressReason::kSettingsOptIn:
case metrics::ConsentBumpSuppressReason::kUserSignedOut:
case metrics::ConsentBumpSuppressReason::kUserTurnedSyncDatatypeOff:
case metrics::ConsentBumpSuppressReason::kUserTurnedPrivacySettingOff:
case metrics::ConsentBumpSuppressReason::kCustomPassphrase:
pref_service_->SetBoolean(prefs::kShouldShowUnifiedConsentBump, false);
break;
case metrics::ConsentBumpSuppressReason::kSyncPaused:
// Consent bump should be shown when sync is active again.
DCHECK(ShouldShowConsentBump());
break;
}
}
void UnifiedConsentService::Shutdown() {
weak_ptr_factory_.InvalidateWeakPtrs();
service_client_->RemoveObserver(this);
identity_manager_->RemoveObserver(this);
sync_service_->RemoveObserver(this);
}
void UnifiedConsentService::OnServiceStateChanged(Service service) {
// Unified consent is disabled when any of its dependent services gets
// disabled.
if (service_client_->GetServiceState(service) == ServiceState::kDisabled &&
IsUnifiedConsentGiven()) {
SetUnifiedConsentGiven(false);
RecordUnifiedConsentRevoked(
metrics::UnifiedConsentRevokeReason::kServiceWasDisabled);
}
}
void UnifiedConsentService::OnPrimaryAccountCleared(
const AccountInfo& account_info) {
// When signing out, the unfied consent is revoked.
if (IsUnifiedConsentGiven()) {
SetUnifiedConsentGiven(false);
RecordUnifiedConsentRevoked(
metrics::UnifiedConsentRevokeReason::kUserSignedOut);
}
pref_service_->SetBoolean(prefs::kAllUnifiedConsentServicesWereEnabled,
false);
// By design, signing out of Chrome automatically disables off-by-default
// services.
pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
false);
service_client_->SetServiceEnabled(Service::kSafeBrowsingExtendedReporting,
false);
service_client_->SetServiceEnabled(Service::kSpellCheck, false);
contextual_search::ContextualSearchPreference::GetInstance()->SetPref(
pref_service_, false);
if (GetMigrationState() != MigrationState::kCompleted) {
// When the user signs out, the migration is complete.
SetMigrationState(MigrationState::kCompleted);
}
if (ShouldShowConsentBump())
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kUserSignedOut);
}
void UnifiedConsentService::OnStateChanged(syncer::SyncService* sync) {
if (sync_service_->GetDisableReasons() !=
syncer::SyncService::DISABLE_REASON_NONE ||
!sync_service_->IsEngineInitialized()) {
return;
}
if (GetMigrationState() == MigrationState::kInProgressWaitForSyncInit)
UpdateSettingsForMigration();
if (sync_service_->GetUserSettings()->IsUsingSecondaryPassphrase()) {
if (ShouldShowConsentBump()) {
// Do not show the consent bump when the user has a custom passphrase.
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kCustomPassphrase);
}
if (IsUnifiedConsentGiven()) {
// Force off unified consent given when the user sets a custom passphrase.
SetUnifiedConsentGiven(false);
RecordUnifiedConsentRevoked(
metrics::UnifiedConsentRevokeReason::kCustomPassphrase);
}
}
if (IsUnifiedConsentGiven() !=
sync_service_->GetUserSettings()->IsSyncEverythingEnabled()) {
// Make sync-everything consistent with the |kUnifiedConsentGiven| pref.
PostTaskToUpdateSyncSettings(/*sync_everything=*/IsUnifiedConsentGiven());
}
}
void UnifiedConsentService::UpdateSyncSettingsIfPossible(
bool sync_everything,
syncer::ModelTypeSet enable_data_types,
syncer::ModelTypeSet disable_data_types) {
DCHECK(Intersection(enable_data_types, disable_data_types).Empty());
if (sync_service_->GetDisableReasons() !=
syncer::SyncService::DISABLE_REASON_NONE ||
!sync_service_->IsEngineInitialized()) {
return;
}
if (sync_everything) {
sync_service_->GetUserSettings()->SetChosenDataTypes(
/*sync_everything=*/true, syncer::UserSelectableTypes());
return;
}
syncer::ModelTypeSet data_types =
sync_service_->GetUserSettings()->GetChosenDataTypes();
data_types.PutAll(enable_data_types);
data_types.RemoveAll(disable_data_types);
data_types.RetainAll(syncer::UserSelectableTypes());
sync_service_->GetUserSettings()->SetChosenDataTypes(
/*sync_everything=*/false, data_types);
}
void UnifiedConsentService::PostTaskToUpdateSyncSettings(
bool sync_everything,
syncer::ModelTypeSet enable_data_types,
syncer::ModelTypeSet disable_data_types) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&UnifiedConsentService::UpdateSyncSettingsIfPossible,
weak_ptr_factory_.GetWeakPtr(), sync_everything,
enable_data_types, disable_data_types));
}
void UnifiedConsentService::OnUnifiedConsentGivenPrefChanged() {
bool enabled = pref_service_->GetBoolean(prefs::kUnifiedConsentGiven);
if (!enabled) {
if (identity_manager_->HasPrimaryAccount() &&
sync_service_->GetUserSettings()->IsSyncEverythingEnabled()) {
UpdateSyncSettingsIfPossible(/*sync_everything=*/false);
}
return;
}
DCHECK(!sync_service_->HasDisableReason(
syncer::SyncService::DISABLE_REASON_PLATFORM_OVERRIDE));
DCHECK(!sync_service_->HasDisableReason(
syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY));
DCHECK(identity_manager_->HasPrimaryAccount());
DCHECK_LT(MigrationState::kNotInitialized, GetMigrationState());
if (GetMigrationState() != MigrationState::kCompleted) {
// If the user opted into unified consent, the migration is completed.
SetMigrationState(MigrationState::kCompleted);
}
if (ShouldShowConsentBump())
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kSettingsOptIn);
// Enable all sync data types if possible, otherwise they will be enabled with
// |OnStateChanged| once sync is active;
autofill::prefs::SetPaymentsIntegrationEnabled(pref_service_, true);
UpdateSyncSettingsIfPossible(/*sync_everything=*/true);
// Enable all non-personalized services.
pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
true);
contextual_search::ContextualSearchPreference::GetInstance()
->OnUnifiedConsentGiven(pref_service_);
// Inform client to enable non-personalized services.
for (int i = 0; i <= static_cast<int>(Service::kLast); ++i) {
Service service = static_cast<Service>(i);
if (service_client_->GetServiceState(service) !=
ServiceState::kNotSupported) {
service_client_->SetServiceEnabled(service, true);
}
}
pref_service_->SetBoolean(prefs::kAllUnifiedConsentServicesWereEnabled, true);
}
MigrationState UnifiedConsentService::GetMigrationState() {
int migration_state_int =
pref_service_->GetInteger(prefs::kUnifiedConsentMigrationState);
DCHECK_LE(static_cast<int>(MigrationState::kNotInitialized),
migration_state_int);
DCHECK_GE(static_cast<int>(MigrationState::kCompleted), migration_state_int);
return static_cast<MigrationState>(migration_state_int);
}
void UnifiedConsentService::SetMigrationState(MigrationState migration_state) {
pref_service_->SetInteger(prefs::kUnifiedConsentMigrationState,
static_cast<int>(migration_state));
}
void UnifiedConsentService::MigrateProfileToUnifiedConsent() {
DCHECK_EQ(GetMigrationState(), MigrationState::kNotInitialized);
DCHECK(!IsUnifiedConsentGiven());
if (!identity_manager_->HasPrimaryAccount()) {
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kNotSignedIn);
SetMigrationState(MigrationState::kCompleted);
return;
}
bool is_syncing_everything =
sync_service_->GetUserSettings()->IsSyncEverythingEnabled();
pref_service_->SetBoolean(prefs::kHadEverythingSyncedBeforeMigration,
is_syncing_everything);
if (!is_syncing_everything) {
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kSyncEverythingOff);
} else if (!AreAllOnByDefaultPrivacySettingsOn()) {
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kPrivacySettingOff);
} else {
// When the user was syncing everything, and all on-by-default privacy
// settings were on, the consent bump should be shown.
pref_service_->SetBoolean(prefs::kShouldShowUnifiedConsentBump, true);
}
UpdateSettingsForMigration();
}
void UnifiedConsentService::UpdateSettingsForMigration() {
if (!sync_service_->IsEngineInitialized()) {
SetMigrationState(MigrationState::kInProgressWaitForSyncInit);
return;
}
if (IsUnifiedConsentGiven()) {
// When the user opted into unified consent through the consent bump or the
// settings page while waiting for sync initialization, the migration is
// completed.
SetMigrationState(MigrationState::kCompleted);
return;
}
// Set URL-keyed anonymized metrics to the state it had before unified
// consent.
bool url_keyed_metrics_enabled =
sync_service_->GetUserSettings()->GetChosenDataTypes().Has(
syncer::TYPED_URLS) &&
!sync_service_->GetUserSettings()->IsUsingSecondaryPassphrase();
pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
url_keyed_metrics_enabled);
// Disable the datatype user events for newly migrated users. Also set
// sync-everything to false, so it matches unified consent given.
PostTaskToUpdateSyncSettings(
/*sync_everything=*/false, /*enable_data_types=*/syncer::ModelTypeSet(),
/*disable_data_types=*/syncer::ModelTypeSet(syncer::USER_EVENTS));
SetMigrationState(MigrationState::kCompleted);
}
bool UnifiedConsentService::AreAllNonPersonalizedServicesEnabled() {
for (int i = 0; i <= static_cast<int>(Service::kLast); ++i) {
Service service = static_cast<Service>(i);
if (service_client_->GetServiceState(service) == ServiceState::kDisabled)
return false;
}
if (!pref_service_->GetBoolean(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled))
return false;
return true;
}
bool UnifiedConsentService::AreAllOnByDefaultPrivacySettingsOn() {
for (auto service : {Service::kAlternateErrorPages,
Service::kMetricsReporting, Service::kNetworkPrediction,
Service::kSafeBrowsing, Service::kSearchSuggest}) {
if (service_client_->GetServiceState(service) == ServiceState::kDisabled)
return false;
}
return true;
}
void UnifiedConsentService::RecordSettingsHistogram() {
bool metric_recorded = false;
if (IsUnifiedConsentGiven()) {
RecordSettingsHistogramSample(
metrics::SettingsHistogramValue::kUnifiedConsentGiven);
metric_recorded = true;
}
if (identity_manager_->HasPrimaryAccount() &&
sync_service_->GetUserSettings()->GetChosenDataTypes().Has(
syncer::USER_EVENTS)) {
RecordSettingsHistogramSample(metrics::SettingsHistogramValue::kUserEvents);
metric_recorded = true;
}
metric_recorded |= RecordSettingsHistogramFromPref(
prefs::kUrlKeyedAnonymizedDataCollectionEnabled, pref_service_,
metrics::SettingsHistogramValue::kUrlKeyedAnonymizedDataCollection);
metric_recorded |= RecordSettingsHistogramFromService(
service_client_.get(),
UnifiedConsentServiceClient::Service::kSafeBrowsingExtendedReporting,
metrics::SettingsHistogramValue::kSafeBrowsingExtendedReporting);
metric_recorded |= RecordSettingsHistogramFromService(
service_client_.get(), UnifiedConsentServiceClient::Service::kSpellCheck,
metrics::SettingsHistogramValue::kSpellCheck);
if (!metric_recorded)
RecordSettingsHistogramSample(metrics::SettingsHistogramValue::kNone);
}
void UnifiedConsentService::CheckConsentBumpEligibility() {
// Only check eligility if the user was eligible before.
if (!ShouldShowConsentBump()) {
metrics::RecordConsentBumpEligibility(false);
return;
}
syncer::ModelTypeSet user_types_without_user_events =
syncer::UserSelectableTypes();
user_types_without_user_events.Remove(syncer::USER_EVENTS);
if (!sync_service_->GetUserSettings()->GetChosenDataTypes().HasAll(
user_types_without_user_events)) {
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kUserTurnedSyncDatatypeOff);
} else if (!AreAllOnByDefaultPrivacySettingsOn()) {
RecordConsentBumpSuppressReason(
metrics::ConsentBumpSuppressReason::kUserTurnedPrivacySettingOff);
}
metrics::RecordConsentBumpEligibility(
pref_service_->GetBoolean(prefs::kShouldShowUnifiedConsentBump));
}
} // namespace unified_consent