| // Copyright 2012 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "components/sync/service/sync_service_impl.h" | |
| #include <cstddef> | |
| #include <utility> | |
| #include "base/check_is_test.h" | |
| #include "base/command_line.h" | |
| #include "base/containers/flat_set.h" | |
| #include "base/containers/to_vector.h" | |
| #include "base/feature_list.h" | |
| #include "base/files/file_path.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback.h" | |
| #include "base/functional/callback_helpers.h" | |
| #include "base/location.h" | |
| #include "base/logging.h" | |
| #include "base/metrics/histogram_functions.h" | |
| #include "base/observer_list.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/task/sequenced_task_runner.h" | |
| #include "base/time/time.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "build/build_config.h" | |
| #include "components/os_crypt/async/browser/os_crypt_async.h" | |
| #include "components/os_crypt/async/common/encryptor.h" | |
| #include "components/signin/public/base/gaia_id_hash.h" | |
| #include "components/signin/public/base/signin_metrics.h" | |
| #include "components/signin/public/base/signin_switches.h" | |
| #include "components/signin/public/identity_manager/account_info.h" | |
| #include "components/signin/public/identity_manager/account_managed_status_finder_outcome.h" | |
| #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" | |
| #include "components/signin/public/identity_manager/identity_manager.h" | |
| #include "components/signin/public/identity_manager/identity_utils.h" | |
| #include "components/signin/public/identity_manager/primary_account_mutator.h" | |
| #include "components/sync/base/command_line_switches.h" | |
| #include "components/sync/base/custom_passphrase_bootstrap_token.h" | |
| #include "components/sync/base/data_type.h" | |
| #include "components/sync/base/data_type_histogram.h" | |
| #include "components/sync/base/features.h" | |
| #include "components/sync/base/stop_source.h" | |
| #include "components/sync/base/sync_util.h" | |
| #include "components/sync/base/user_selectable_type.h" | |
| #include "components/sync/engine/configure_reason.h" | |
| #include "components/sync/engine/engine_components_factory_impl.h" | |
| #include "components/sync/engine/net/http_post_provider_factory.h" | |
| #include "components/sync/engine/shutdown_reason.h" | |
| #include "components/sync/engine/sync_encryption_handler.h" | |
| #include "components/sync/invalidations/sync_invalidations_service.h" | |
| #include "components/sync/service/backend_migrator.h" | |
| #include "components/sync/service/bookmark_sync_error_state.h" | |
| #include "components/sync/service/configure_context.h" | |
| #include "components/sync/service/data_type_manager_impl.h" | |
| #include "components/sync/service/data_type_status_table.h" | |
| #include "components/sync/service/device_statistics_request_impl.h" | |
| #include "components/sync/service/glue/sync_transport_data_prefs.h" | |
| #include "components/sync/service/local_data_description.h" | |
| #include "components/sync/service/local_data_migration_item_queue.h" | |
| #include "components/sync/service/sync_auth_manager.h" | |
| #include "components/sync/service/sync_engine_factory.h" | |
| #include "components/sync/service/sync_error.h" | |
| #include "components/sync/service/sync_feature_status_for_migrations_recorder.h" | |
| #include "components/sync/service/sync_prefs.h" | |
| #include "components/sync/service/sync_prefs_policy_handler.h" | |
| #include "components/sync/service/sync_service_utils.h" | |
| #include "components/sync/service/trusted_vault_histograms.h" | |
| #include "components/sync/service/trusted_vault_synthetic_field_trial.h" | |
| #include "google_apis/gaia/google_service_auth_error.h" | |
| #include "services/network/public/cpp/shared_url_loader_factory.h" | |
| #if BUILDFLAG(IS_ANDROID) | |
| #include "base/android/device_info.h" | |
| #include "base/android/jni_android.h" | |
| #include "base/android/jni_array.h" | |
| #include "base/android/jni_string.h" | |
| #include "base/memory/scoped_refptr.h" | |
| #include "components/password_manager/core/browser/split_stores_and_local_upm.h" | |
| #include "components/sync/android/jni_headers/ExplicitPassphrasePlatformClient_jni.h" | |
| #include "components/sync/android/sync_service_android_bridge.h" | |
| #include "components/sync/model/crypto/nigori.h" | |
| #include "components/sync/protocol/nigori_specifics.pb.h" | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| namespace syncer { | |
| namespace { | |
| #if BUILDFLAG(IS_ANDROID) | |
| constexpr int kMinGmsVersionCodeWithCustomPassphraseApi = 235204000; | |
| // Keep in sync with the corresponding string in | |
| // ExplicitPassphrasePlatformClientTest.java | |
| constexpr char kIgnoreMinGmsVersionWithPassphraseSupportForTest[] = | |
| "ignore-min-gms-version-with-passphrase-support-for-test"; | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| // The initial state of sync, for the Sync.InitialState2 histogram. Even if | |
| // this value indicates that sync (the feature or the transport) can start, the | |
| // startup might fail for reasons such as network issues, or the version of | |
| // Chrome being too old. | |
| // These values are persisted to logs. Entries should not be renumbered and | |
| // numeric values should never be reused. | |
| // LINT.IfChange(SyncInitialState) | |
| enum SyncInitialState { | |
| // Sync-the-feature can attempt to start up. | |
| kFeatureCanStart = 0, | |
| // There is no signed in user, so neither feature nor transport can start. | |
| kNotSignedIn = 1, | |
| // The user has disabled Sync-the-feature, but the initial setup has been | |
| // completed. This should be very rare; it can happen after a | |
| // reset-via-dashboard on ChromeOS. | |
| kFeatureNotRequested = 2, | |
| // The user has not enabled Sync-the-feature. This is the expected state for | |
| // a Sync-the-transport (signed-in non-syncing) user. | |
| kFeatureNotRequestedNotSetup = 3, | |
| // The user has enabled Sync-the-feature, but has not completed the initial | |
| // setup. This should be rare; it can happen if the initial setup got | |
| // interrupted e.g. by a crash. | |
| kFeatureNotSetup = 4, | |
| // Sync (both feature and transport) is disallowed by enterprise policy. | |
| kNotAllowedByPolicy = 5, | |
| kObsoleteNotAllowedByPlatform = 6, | |
| kMaxValue = kObsoleteNotAllowedByPlatform | |
| }; | |
| // LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncInitialState) | |
| // These values are persisted to logs. Entries should not be renumbered and | |
| // numeric values should never be reused. | |
| enum class DownloadStatusWaitingForUpdatesReason { | |
| kRefreshTokensNotLoaded = 0, | |
| kSyncEngineNotInitialized = 1, | |
| kDataTypeNotActive = 2, | |
| kInvalidationsNotInitialized = 3, | |
| kIncomingInvalidation = 4, | |
| kPollRequestScheduled = 5, | |
| kMaxValue = kPollRequestScheduled | |
| }; | |
| void RecordSyncInitialState(SyncService::DisableReasonSet disable_reasons, | |
| bool is_sync_feature_requested, | |
| bool initial_sync_feature_setup_complete) { | |
| SyncInitialState sync_state = kFeatureCanStart; | |
| if (disable_reasons.Has(SyncService::DISABLE_REASON_NOT_SIGNED_IN)) { | |
| sync_state = kNotSignedIn; | |
| } else if (disable_reasons.Has( | |
| SyncService::DISABLE_REASON_ENTERPRISE_POLICY)) { | |
| sync_state = kNotAllowedByPolicy; | |
| } else if (!is_sync_feature_requested) { | |
| if (initial_sync_feature_setup_complete) { | |
| sync_state = kFeatureNotRequested; | |
| } else { | |
| sync_state = kFeatureNotRequestedNotSetup; | |
| } | |
| } else if (!initial_sync_feature_setup_complete) { | |
| sync_state = kFeatureNotSetup; | |
| } | |
| base::UmaHistogramEnumeration("Sync.InitialState2", sync_state); | |
| } | |
| EngineComponentsFactory::Switches EngineSwitchesFromCommandLine() { | |
| EngineComponentsFactory::Switches factory_switches = { | |
| EngineComponentsFactory::BACKOFF_NORMAL, | |
| /*force_short_nudge_delay_for_test=*/false}; | |
| const base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); | |
| if (cl->HasSwitch(kSyncShortInitialRetryOverride)) { | |
| factory_switches.backoff_override = | |
| EngineComponentsFactory::BACKOFF_SHORT_INITIAL_RETRY_OVERRIDE; | |
| } | |
| if (cl->HasSwitch(kSyncShortNudgeDelayForTest)) { | |
| factory_switches.force_short_nudge_delay_for_test = true; | |
| } | |
| return factory_switches; | |
| } | |
| base::TimeDelta GetDeferredInitDelay() { | |
| if (base::FeatureList::IsEnabled(kDeferredSyncStartupCustomDelay)) { | |
| return base::Seconds(kDeferredSyncStartupCustomDelayInSeconds.Get()); | |
| } | |
| const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | |
| if (cmdline->HasSwitch(kSyncDeferredStartupTimeoutSeconds)) { | |
| int timeout = 0; | |
| if (base::StringToInt( | |
| cmdline->GetSwitchValueASCII(kSyncDeferredStartupTimeoutSeconds), | |
| &timeout)) { | |
| DCHECK_GE(timeout, 0); | |
| return base::Seconds(timeout); | |
| } | |
| } | |
| return base::Seconds(10); | |
| } | |
| void MaybeClearAccountKeyedPreferences( | |
| signin::IdentityManager* identity_manager, | |
| const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, | |
| SyncUserSettingsImpl& user_settings) { | |
| #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) | |
| if (accounts_in_cookie_jar_info.AreAccountsFresh()) { | |
| // Clear settings for accounts no longer in the cookie jar. On Android | |
| // and iOS this is done when the account is removed from the OS instead. | |
| std::vector<GaiaId> gaia_ids = | |
| base::ToVector(signin::GetAllGaiaIdsForKeyedPreferences( | |
| identity_manager, accounts_in_cookie_jar_info)); | |
| user_settings.KeepAccountSettingsPrefsOnlyForUsers(gaia_ids); | |
| } | |
| #endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) | |
| } | |
| } // namespace | |
| SyncServiceImpl::InitParams::InitParams() = default; | |
| SyncServiceImpl::InitParams::InitParams(InitParams&& other) = default; | |
| SyncServiceImpl::InitParams::~InitParams() = default; | |
| SyncServiceImpl::SyncServiceImpl(InitParams init_params) | |
| : sync_client_(std::move(init_params.sync_client)), | |
| create_http_post_provider_factory_( | |
| std::move(init_params.create_http_post_provider_factory)), | |
| os_crypt_async_(init_params.os_crypt_async), | |
| sync_prefs_(sync_client_->GetPrefService()), | |
| identity_manager_(sync_prefs_.IsLocalSyncEnabled() | |
| ? nullptr | |
| : sync_client_->GetIdentityManager()), | |
| auth_manager_(std::make_unique<SyncAuthManager>( | |
| identity_manager_, | |
| /*delegate=*/this, | |
| init_params.account_managed_status_finder_timeout)), | |
| channel_(init_params.channel), | |
| debug_identifier_(std::move(init_params.debug_identifier)), | |
| sync_service_url_( | |
| GetSyncServiceURL(*base::CommandLine::ForCurrentProcess(), channel_)), | |
| crypto_(this, sync_client_->GetTrustedVaultClient()), | |
| url_loader_factory_(std::move(init_params.url_loader_factory)), | |
| network_connection_tracker_( | |
| std::move(init_params.network_connection_tracker)) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| DCHECK(sync_client_); | |
| DCHECK(IsLocalSyncEnabled() || identity_manager_ != nullptr); | |
| CHECK(os_crypt_async_); | |
| // If Sync is disabled via command line flag, then SyncServiceImpl | |
| // shouldn't be instantiated. | |
| DCHECK(IsSyncAllowedByFlag()); | |
| sync_stopped_reporter_ = std::make_unique<SyncStoppedReporter>( | |
| sync_service_url_, MakeUserAgentForSync(channel_), url_loader_factory_); | |
| if (identity_manager_) { | |
| identity_manager_observation_.Observe(identity_manager_); | |
| } | |
| observers_.emplace(); | |
| // Based on the information cached in preferences, it might be required to | |
| // register a synthetic field trial group. This should be done as early as | |
| // possible to avoid untagged metrics if they get logged before other events | |
| // like sync engine initialization, which could take arbitrarily long (e.g. | |
| // persistent auth error). Task-posting is involved to avoid infinite | |
| // recursions if the implementation in SyncClient leads to | |
| // accessing/constructing SyncService. | |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( | |
| FROM_HERE, | |
| base::BindOnce( | |
| &SyncServiceImpl::RegisterTrustedVaultSyntheticFieldTrialsIfNecessary, | |
| weak_factory_.GetWeakPtr())); | |
| } | |
| void SyncServiceImpl::RegisterTrustedVaultSyntheticFieldTrialsIfNecessary() { | |
| if (trusted_vault_auto_upgrade_synthetic_field_trial_registered_) { | |
| // Registration function already invoked. It cannot be invoked twice, as | |
| // runtime changes to the group assignment is not supported (e.g. signout). | |
| return; | |
| } | |
| const sync_pb::TrustedVaultAutoUpgradeExperimentGroup proto = | |
| sync_prefs_.GetCachedTrustedVaultAutoUpgradeExperimentGroup().value_or( | |
| sync_pb::TrustedVaultAutoUpgradeExperimentGroup()); | |
| const TrustedVaultAutoUpgradeSyntheticFieldTrialGroup group = | |
| TrustedVaultAutoUpgradeSyntheticFieldTrialGroup::FromProto(proto); | |
| if (!group.is_valid()) { | |
| // Broadcasting an invalid group isn't allowed, as it would otherwise use | |
| // the only chance to invoke the registration function below, which may only | |
| // be invoked once. | |
| return; | |
| } | |
| trusted_vault_auto_upgrade_synthetic_field_trial_registered_ = true; | |
| sync_client_->RegisterTrustedVaultAutoUpgradeSyntheticFieldTrial(group); | |
| } | |
| SyncServiceImpl::~SyncServiceImpl() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Shutdown() should have been called before destruction. | |
| DCHECK(!engine_); | |
| } | |
| void SyncServiceImpl::Initialize(DataTypeController::TypeVector controllers) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| data_type_manager_ = std::make_unique<DataTypeManagerImpl>( | |
| std::move(controllers), &crypto_, this); | |
| // It's safe to pass a raw ptr, since SyncServiceImpl outlives | |
| // SyncUserSettingsImpl. | |
| user_settings_ = std::make_unique<SyncUserSettingsImpl>( | |
| /*delegate=*/this, &crypto_, &sync_prefs_, | |
| data_type_manager_->GetRegisteredDataTypes()); | |
| if (!IsLocalSyncEnabled()) { | |
| auth_manager_->RegisterForAuthNotifications(); | |
| // Trigger a refresh when additional data types get enabled for | |
| // invalidations. This is needed to get the latest data after subscribing | |
| // for the updates. | |
| sync_client_->GetSyncInvalidationsService() | |
| ->SetCommittedAdditionalInterestedDataTypesCallback(base::BindRepeating( | |
| &SyncServiceImpl::TriggerRefresh, weak_factory_.GetWeakPtr(), | |
| TriggerRefreshSource::kSyncInvalidationsService)); | |
| // TODO(crbug.com/40257467): revisit this logic. IsSignedIn() doesn't feel | |
| // the right condition to check. | |
| if (IsSignedIn()) { | |
| // Start receiving invalidations as soon as possible since GCMDriver drops | |
| // incoming FCM messages otherwise. The messages will be collected by | |
| // SyncInvalidationsService until sync engine is initialized and ready to | |
| // handle invalidations. | |
| sync_client_->GetSyncInvalidationsService()->StartListening(); | |
| } | |
| } | |
| // *After* setting up `auth_manager_`, run pref migrations that depend on | |
| // the account state. | |
| sync_prefs_.MaybeMigratePrefsForSyncToSigninPart1( | |
| GetSyncAccountStateForPrefs(), GetAccountInfo().gaia); | |
| // Update selected types prefs if a policy is applied. | |
| sync_prefs_policy_handler_ = std::make_unique<SyncPrefsPolicyHandler>(this); | |
| // If sync is disabled permanently, clean up old data that may be around (e.g. | |
| // crash during signout). | |
| if (HasDisableReason(DISABLE_REASON_ENTERPRISE_POLICY)) { | |
| StopAndClear(ResetEngineReason::kEnterprisePolicy); | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| // Disable OS data types to avoid automatic local data upload upon policy | |
| // removal, as OS data types do not support dual storage with UNO. | |
| if (!HasSyncConsent() && IsReplaceSyncPromosWithSignInPromosEnabled()) { | |
| user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/false, | |
| UserSelectableOsTypeSet()); | |
| } else { | |
| // On ChromeOS Ash, sync-the-feature stays disabled even after the policy | |
| // is removed, for historic reasons. It is unclear if this behavior is | |
| // optional, because it is indistinguishable from the | |
| // sync-reset-via-dashboard case. It can be resolved by invoking | |
| // ClearSyncFeatureDisabledViaDashboard(). | |
| user_settings_->SetSyncFeatureDisabledViaDashboard(); | |
| } | |
| #endif // BUILDFLAG(IS_CHROMEOS) | |
| } else if (HasDisableReason(DISABLE_REASON_NOT_SIGNED_IN)) { | |
| // On ChromeOS-Ash, signout is not possible, so it's not necessary to handle | |
| // this case. | |
| // TODO(crbug.com/40272157): It *should* be harmless to handle this case on | |
| // ChromeOS-Ash since it's supposedly unreachable, *but* during the very | |
| // first startup of a fresh profile, the signed-in account isn't known yet | |
| // at this point (see also https://crbug.com/1458701#c7). | |
| #if !BUILDFLAG(IS_CHROMEOS) | |
| StopAndClear(ResetEngineReason::kNotSignedIn); | |
| #endif | |
| } | |
| const bool is_sync_feature_requested_for_metrics = | |
| IsLocalSyncEnabled() || | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| (!user_settings_->IsSyncFeatureDisabledViaDashboard() && | |
| (!base::FeatureList::IsEnabled(kReplaceSyncPromosWithSignInPromos) || | |
| HasSyncConsent())); | |
| #else | |
| HasSyncConsent(); | |
| #endif // BUILDFLAG(IS_CHROMEOS) | |
| // Note: We need to record the initial state *after* calling | |
| // RegisterForAuthNotifications(), because before that the authenticated | |
| // account isn't initialized. | |
| RecordSyncInitialState(GetDisableReasons(), | |
| is_sync_feature_requested_for_metrics, | |
| user_settings_->IsInitialSyncFeatureSetupComplete()); | |
| // Call Stop() on controllers for non-preferred types to clear metadata. | |
| // This allows clearing metadata for types disabled in previous run early-on | |
| // during initialization. | |
| data_type_manager_->ClearMetadataWhileStoppedExceptFor( | |
| GetPreferredDataTypes()); | |
| if (IsEngineAllowedToRun()) { | |
| if (!sync_client_->GetSyncEngineFactory() | |
| ->HasTransportDataIncludingFirstSync( | |
| signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia))) { | |
| // Sync never initialized before on this profile, so let's try immediately | |
| // the very first time. This is particularly useful for Chrome Ash (where | |
| // the user is signed in to begin with) and local sync (where sign-in | |
| // state doesn't matter to start the engine). | |
| TryStart(); | |
| } else { | |
| // Defer starting the engine, for browser startup performance. If another | |
| // TryStart() happens in the meantime, this deferred task will no-op. | |
| deferring_first_start_since_ = base::Time::Now(); | |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( | |
| FROM_HERE, | |
| base::BindOnce(&SyncServiceImpl::TryStart, | |
| weak_factory_.GetWeakPtr()), | |
| GetDeferredInitDelay()); | |
| } | |
| } | |
| sync_status_recorder_ = | |
| std::make_unique<SyncFeatureStatusForMigrationsRecorder>( | |
| sync_client_->GetPrefService(), this); | |
| local_data_migration_item_queue_ = | |
| std::make_unique<LocalDataMigrationItemQueue>(this, | |
| data_type_manager_.get()); | |
| device_statistics_scheduler_ = std::make_unique<DeviceStatisticsScheduler>( | |
| /*delegate=*/this, sync_client_->GetPrefService(), | |
| sync_client_->GetIdentityManager(), sync_service_url_); | |
| } | |
| void SyncServiceImpl::StartSyncingWithServer() { | |
| if (engine_) { | |
| engine_->StartSyncingWithServer(); | |
| } | |
| if (IsLocalSyncEnabled()) { | |
| TriggerRefresh(TriggerRefreshSource::kLocalSync, DataTypeSet::All()); | |
| } | |
| } | |
| DataTypeSet SyncServiceImpl::GetRegisteredDataTypesForTest() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| return data_type_manager_->GetRegisteredDataTypes(); | |
| } | |
| bool SyncServiceImpl::HasAnyModelErrorForTest(DataTypeSet types) const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| CHECK(data_type_manager_); | |
| for (DataType type : types) { | |
| DataTypeController* controller = | |
| data_type_manager_->GetControllerForTest(type); // IN-TEST | |
| if (controller && controller->state() == DataTypeController::FAILED) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void SyncServiceImpl::GetThrottledDataTypesForTest( | |
| base::OnceCallback<void(DataTypeSet)> cb) const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| if (!engine_ || !engine_->IsInitialized()) { | |
| std::move(cb).Run(DataTypeSet()); | |
| return; | |
| } | |
| engine_->GetThrottledDataTypesForTest(std::move(cb)); // IN-TEST | |
| } | |
| size_t SyncServiceImpl::GetQueuedLocalDataMigrationItemCountForTest() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| return local_data_migration_item_queue_ | |
| ->GetItemsCountForTesting(); // IN-TEST | |
| } | |
| // static | |
| ShutdownReason SyncServiceImpl::ShutdownReasonForResetEngineReason( | |
| ResetEngineReason reset_reason) { | |
| switch (reset_reason) { | |
| case ResetEngineReason::kShutdown: | |
| return ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA; | |
| case ResetEngineReason::kCredentialsChanged: | |
| return ShutdownReason::STOP_SYNC_AND_KEEP_DATA; | |
| case ResetEngineReason::kUnrecoverableError: | |
| case ResetEngineReason::kDisabledAccount: | |
| case ResetEngineReason::kResetLocalData: | |
| case ResetEngineReason::kUpgradeClientError: | |
| case ResetEngineReason::kNotSignedIn: | |
| case ResetEngineReason::kEnterprisePolicy: | |
| case ResetEngineReason::kDisableSyncOnClient: | |
| return ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA; | |
| } | |
| } | |
| bool SyncServiceImpl::ShouldClearTransportDataForAccount( | |
| ResetEngineReason reset_reason) { | |
| switch (reset_reason) { | |
| case ResetEngineReason::kShutdown: | |
| case ResetEngineReason::kDisabledAccount: | |
| case ResetEngineReason::kUpgradeClientError: | |
| case ResetEngineReason::kCredentialsChanged: | |
| case ResetEngineReason::kNotSignedIn: | |
| case ResetEngineReason::kEnterprisePolicy: | |
| // Regular/benign cases; no need to clear. | |
| return false; | |
| case ResetEngineReason::kUnrecoverableError: | |
| case ResetEngineReason::kResetLocalData: | |
| case ResetEngineReason::kDisableSyncOnClient: | |
| // Weird error, or explicit request to reset. Clear transport data to | |
| // start over fresh. | |
| return true; | |
| } | |
| } | |
| bool SyncServiceImpl::IsEngineAllowedToRun() const { | |
| return GetDisableReasons().empty() && !auth_manager_->IsSyncPaused(); | |
| } | |
| void SyncServiceImpl::OnProtocolEvent(const ProtocolEvent& event) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| for (ProtocolEventObserver& observer : protocol_event_observers_) { | |
| observer.OnProtocolEvent(event); | |
| } | |
| } | |
| void SyncServiceImpl::OnDataTypeRequestsSyncStartup(DataType type) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| DCHECK(UserTypes().Has(type)); | |
| if (!GetPreferredDataTypes().Has(type)) { | |
| // We can get here as datatype SyncableServices are typically wired up | |
| // to the native datatype even if sync isn't enabled. | |
| DVLOG(1) << "Dropping sync startup request because type " | |
| << DataTypeToDebugString(type) << "not enabled."; | |
| return; | |
| } | |
| if (engine_) { | |
| DVLOG(1) << "A data type requested sync startup, but it looks like " | |
| "something else beat it to the punch."; | |
| return; | |
| } | |
| TryStart(); | |
| } | |
| void SyncServiceImpl::TryStart() { | |
| CHECK(os_crypt_async_); | |
| // It's possible for this to be called multiple times before the callback | |
| // runs (e.g. if the user signs out and back in again). This is safe, as | |
| // OSCryptAsync will just queue the callbacks and run them once the | |
| // encryptor is available. The first call to TryStartImpl() that succeeds | |
| // will create the engine, and subsequent ones will be no-ops. | |
| os_crypt_async_->GetInstance(base::BindOnce(&SyncServiceImpl::TryStartImpl, | |
| weak_factory_.GetWeakPtr(), | |
| base::TimeTicks::Now())); | |
| } | |
| void SyncServiceImpl::TryStartImpl( | |
| base::TimeTicks try_start_time, | |
| scoped_refptr<os_crypt_async::Encryptor> encryptor) { | |
| base::Time deferral_time; | |
| std::swap(deferring_first_start_since_, deferral_time); | |
| if (engine_ || !IsEngineAllowedToRun()) { | |
| return; | |
| } | |
| CHECK(encryptor); | |
| base::UmaHistogramTimes("Sync.EncryptorReceivedTime", | |
| base::TimeTicks::Now() - try_start_time); | |
| crypto_.SetEncryptor(encryptor); | |
| if (!deferral_time.is_null()) { | |
| base::UmaHistogramCustomTimes("Sync.Startup.TimeDeferred2", | |
| base::Time::Now() - deferral_time, | |
| base::Seconds(0), base::Minutes(2), 60); | |
| } | |
| const CoreAccountInfo authenticated_account_info = GetAccountInfo(); | |
| if (IsLocalSyncEnabled()) { | |
| // With local sync (roaming profiles) there is no identity manager and hence | |
| // `authenticated_account_info` is empty. This is required for | |
| // IsLocalSyncTransportDataValid() to work properly. | |
| DCHECK(authenticated_account_info.gaia.empty()); | |
| DCHECK(authenticated_account_info.account_id.empty()); | |
| } else { | |
| // Except for local sync (roaming profiles), the user must be signed in for | |
| // sync to start. | |
| DCHECK(!authenticated_account_info.gaia.empty()); | |
| DCHECK(!authenticated_account_info.account_id.empty()); | |
| } | |
| engine_ = sync_client_->GetSyncEngineFactory()->CreateSyncEngine( | |
| debug_identifier_, | |
| signin::GaiaIdHash::FromGaiaId(authenticated_account_info.gaia), | |
| sync_client_->GetSyncInvalidationsService()); | |
| DCHECK(engine_); | |
| // Clear any old errors the first time sync starts. | |
| if (!user_settings_->IsInitialSyncFeatureSetupComplete()) { | |
| last_actionable_error_ = SyncProtocolError(); | |
| } | |
| SyncEngine::InitParams params; | |
| params.host = this; | |
| params.encryption_observer_proxy = crypto_.GetEncryptionObserverProxy(); | |
| params.extensions_activity = sync_client_->GetExtensionsActivity(); | |
| params.authenticated_account_info = authenticated_account_info; | |
| params.sync_manager_factory = | |
| std::make_unique<SyncManagerFactory>(network_connection_tracker_); | |
| if (sync_prefs_.IsLocalSyncEnabled()) { | |
| params.engine_components_factory = | |
| EngineComponentsFactoryImpl::CreateForLocalSync( | |
| EngineSwitchesFromCommandLine(), | |
| sync_client_->GetLocalSyncBackendFolder()); | |
| } else { | |
| params.engine_components_factory = | |
| EngineComponentsFactoryImpl::CreateForServerSync( | |
| EngineSwitchesFromCommandLine(), sync_service_url_, | |
| base::BindOnce( | |
| create_http_post_provider_factory_override_for_test_.value_or( | |
| create_http_post_provider_factory_), | |
| MakeUserAgentForSync(channel_), url_loader_factory_->Clone())); | |
| } | |
| params.encryptor = std::move(encryptor); | |
| if (!IsLocalSyncEnabled()) { | |
| auth_manager_->ConnectionOpened(); | |
| // Ensures that invalidations are enabled, e.g. when the sync was just | |
| // enabled or after the engine was stopped with clearing data. Note that | |
| // invalidations are not supported for local sync. | |
| sync_client_->GetSyncInvalidationsService()->StartListening(); | |
| } | |
| engine_->Initialize(std::move(params)); | |
| } | |
| void SyncServiceImpl::Shutdown() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| TRACE_EVENT0("sync", "SyncServiceImpl::Shutdown"); | |
| is_shutting_down_ = true; | |
| NotifyShutdown(); | |
| device_statistics_scheduler_.reset(); | |
| // Ensure the LocalDataMigrationItemQueue, the DataTypeManager and the | |
| // engine are destroyed in order since they hold consecutive pointers to each | |
| // other. | |
| std::unique_ptr<SyncEngine> engine = | |
| ResetEngine(ResetEngineReason::kShutdown); | |
| local_data_migration_item_queue_.reset(); | |
| data_type_manager_.reset(); | |
| engine.reset(); | |
| crypto_.StopObservingTrustedVaultClient(); | |
| // All observers must be gone now: All KeyedServices should have unregistered | |
| // their observers already before, in their own Shutdown(), and all others | |
| // should have done it now when they got the shutdown notification. | |
| // (Note that destroying the ObserverList triggers its "check_empty" check.) | |
| observers_.reset(); | |
| auth_manager_.reset(); | |
| identity_manager_observation_.Reset(); | |
| } | |
| std::unique_ptr<SyncEngine> SyncServiceImpl::ResetEngine( | |
| ResetEngineReason reset_reason) { | |
| TRACE_EVENT0("sync", "SyncServiceImpl::ResetEngine"); | |
| CHECK(data_type_manager_); | |
| tasks_waiting_for_engine_initialization_.clear(); | |
| const ShutdownReason shutdown_reason = | |
| ShutdownReasonForResetEngineReason(reset_reason); | |
| // Stop all data type controllers, if needed. Note that until Stop completes, | |
| // it is possible in theory to have a ChangeProcessor apply a change from a | |
| // native model. In that case, it will get applied to the local storage as an | |
| // unsynced change. That will be persisted, and committed on restart. | |
| if (shutdown_reason != ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA) { | |
| data_type_manager_->Stop( | |
| ShutdownReasonToSyncStopMetadataFate(shutdown_reason)); | |
| data_type_manager_->SetConfigurer(nullptr); | |
| } | |
| if (!engine_) { | |
| // If the engine hasn't started or is already shut down when a DISABLE_SYNC | |
| // happens, the Directory needs to be cleaned up here. | |
| if (shutdown_reason == ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA) { | |
| sync_client_->GetSyncEngineFactory()->CleanupOnDisableSync(); | |
| } | |
| // Depending on the `reset_reason`, maybe clear account-keyed transport | |
| // data. | |
| if (ShouldClearTransportDataForAccount(reset_reason)) { | |
| sync_client_->GetSyncEngineFactory()->ClearTransportDataForAccount( | |
| signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia)); | |
| } | |
| return nullptr; | |
| } | |
| base::UmaHistogramEnumeration("Sync.ResetEngineReason", reset_reason); | |
| switch (shutdown_reason) { | |
| case ShutdownReason::STOP_SYNC_AND_KEEP_DATA: | |
| // Do not stop listening for sync invalidations. Otherwise, GCMDriver | |
| // would drop all the incoming messages. | |
| RemoveClientFromServer(); | |
| break; | |
| case ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA: { | |
| sync_client_->GetSyncInvalidationsService()->StopListeningPermanently(); | |
| RemoveClientFromServer(); | |
| break; | |
| } | |
| case ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA: | |
| sync_client_->GetSyncInvalidationsService()->StopListening(); | |
| break; | |
| } | |
| // First, we spin down the engine to stop change processing as soon as | |
| // possible. | |
| engine_->StopSyncingForShutdown(); | |
| // Shutdown the migrator before the engine to ensure it doesn't pull a null | |
| // snapshot. | |
| migrator_.reset(); | |
| engine_->Shutdown(shutdown_reason); | |
| std::unique_ptr<SyncEngine> engine_to_be_destroyed = std::move(engine_); | |
| // Clear various state. | |
| crypto_.Reset(); | |
| last_snapshot_ = SyncCycleSnapshot(); | |
| // Depending on the `reset_reason`, maybe clear account-keyed transport data. | |
| if (ShouldClearTransportDataForAccount(reset_reason)) { | |
| sync_client_->GetSyncEngineFactory()->ClearTransportDataForAccount( | |
| signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia)); | |
| } | |
| if (!IsLocalSyncEnabled()) { | |
| auth_manager_->ConnectionClosed(); | |
| } | |
| DVLOG(2) << "Notify observers on reset engine"; | |
| NotifyObservers(); | |
| // Now that everything is shut down, try to start up again. | |
| switch (shutdown_reason) { | |
| case ShutdownReason::STOP_SYNC_AND_KEEP_DATA: | |
| case ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA: | |
| // If Sync is being stopped (either temporarily or permanently), | |
| // immediately try to start up again. Note that this might start only the | |
| // transport mode, or it might not start anything at all if something is | |
| // preventing Sync startup (e.g. the user signed out). | |
| // Note that TryStart() is guaranteed to *not* have a synchronous effect | |
| // (it posts a task). | |
| TryStart(); | |
| break; | |
| case ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA: | |
| // The only exception is browser shutdown: In this case, there's clearly | |
| // no point in starting up again. | |
| break; | |
| } | |
| return engine_to_be_destroyed; | |
| } | |
| #if BUILDFLAG(IS_ANDROID) | |
| base::android::ScopedJavaLocalRef<jobject> SyncServiceImpl::GetJavaObject() { | |
| if (!sync_service_android_) { | |
| sync_service_android_ = std::make_unique<SyncServiceAndroidBridge>(this); | |
| } | |
| return sync_service_android_->GetJavaObject(); | |
| } | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| SyncUserSettings* SyncServiceImpl::GetUserSettings() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return user_settings_.get(); | |
| } | |
| const SyncUserSettings* SyncServiceImpl::GetUserSettings() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return user_settings_.get(); | |
| } | |
| SyncService::DisableReasonSet SyncServiceImpl::GetDisableReasons() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // If Sync is disabled via command line flag, then SyncServiceImpl | |
| // shouldn't even be instantiated. | |
| DCHECK(IsSyncAllowedByFlag()); | |
| DisableReasonSet result; | |
| // If local sync is enabled, most disable reasons don't apply. | |
| if (!IsLocalSyncEnabled()) { | |
| if (user_settings_->IsSyncClientDisabledByPolicy() || | |
| sync_disabled_by_admin_) { | |
| result.Put(DISABLE_REASON_ENTERPRISE_POLICY); | |
| } | |
| if (!IsSignedIn()) { | |
| result.Put(DISABLE_REASON_NOT_SIGNED_IN); | |
| } | |
| } | |
| if (unrecoverable_error_reason_) { | |
| result.Put(DISABLE_REASON_UNRECOVERABLE_ERROR); | |
| } | |
| return result; | |
| } | |
| SyncService::TransportState SyncServiceImpl::GetTransportState() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (!GetDisableReasons().empty()) { | |
| // Note: we generally shouldn't have an engine while in a disabled state, | |
| // but it can happen if this method gets called during ResetEngine(). | |
| return TransportState::DISABLED; | |
| } | |
| if (auth_manager_->IsSyncPaused()) { | |
| return TransportState::PAUSED; | |
| } | |
| CHECK(IsEngineAllowedToRun()); | |
| if (!engine_) { | |
| // Starting the engine is allowed but didn't happen. There are three | |
| // possible scenarios: | |
| // 1) Startup was deferred, in which case it can take noticeably long until | |
| // . the engine initializes. This case can be distinguished by checking if | |
| // . `deferring_first_start_since_` is set. | |
| // 2) Startup is about to happen because SyncServiceImpl::TryStart() was | |
| // . invoked, but the posted task to run SyncServiceImpl::TryStartImpl() | |
| // . hasn't been processed yet. | |
| // 3) The service is shutting down. | |
| // | |
| // This function reports TransportState::START_DEFERRED only for the first, | |
| // which is the only real deferred case. | |
| return deferring_first_start_since_.is_null() | |
| ? TransportState::INITIALIZING | |
| : TransportState::START_DEFERRED; | |
| } | |
| if (!engine_->IsInitialized() || !data_type_manager_) { | |
| return TransportState::INITIALIZING; | |
| } | |
| // Determining the account's managed-ness status is also considered part of | |
| // initialization. | |
| if (!IsLocalSyncEnabled() && | |
| auth_manager_->GetActiveAccountInfo().managed_status == | |
| signin::AccountManagedStatusFinderOutcome::kPending) { | |
| return TransportState::INITIALIZING; | |
| } | |
| // At this point we should usually be able to configure our data types (so the | |
| // DataTypeManager should not be STOPPED anymore), unless setup is in | |
| // progress. But it can also happen if this gets called from DataTypeManager | |
| // itself. | |
| if (data_type_manager_->state() == DataTypeManager::STOPPED) { | |
| return TransportState::PENDING_DESIRED_CONFIGURATION; | |
| } | |
| if (data_type_manager_->state() != DataTypeManager::CONFIGURED) { | |
| return TransportState::CONFIGURING; | |
| } | |
| return TransportState::ACTIVE; | |
| } | |
| SyncService::UserActionableError SyncServiceImpl::GetUserActionableError() | |
| const { | |
| #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) | |
| if (HasSyncConsent()) { | |
| if (!GetUserSettings()->IsInitialSyncFeatureSetupComplete()) { | |
| return UserActionableError::kNeedsSettingsConfirmation; | |
| } | |
| // UPGRADE_CLIENT error is unrecoverable, but is treated separately below. | |
| if (HasUnrecoverableError() && | |
| last_actionable_error_.action != UPGRADE_CLIENT) { | |
| return UserActionableError::kUnrecoverableError; | |
| } | |
| } | |
| #endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) | |
| if (GetAuthError().state() != GoogleServiceAuthError::NONE) { | |
| #if BUILDFLAG(IS_IOS) | |
| if (GetAuthError().state() == | |
| GoogleServiceAuthError::DEVICE_MANAGEMENT_ERROR && | |
| base::FeatureList::IsEnabled( | |
| switches::kHandleMdmErrorsForDasherAccounts)) { | |
| return UserActionableError::kDeviceManagementError; | |
| } | |
| #elif BUILDFLAG(IS_ANDROID) | |
| // TODO(crbug.com/530541220): Merge with the iOS section during the feature | |
| // flag cleanup. | |
| if (GetAuthError().state() == | |
| GoogleServiceAuthError::DEVICE_MANAGEMENT_ERROR && | |
| base::FeatureList::IsEnabled( | |
| switches::kHandleMdmErrorsForDasherAccountsOnAndroid)) { | |
| return UserActionableError::kDeviceManagementError; | |
| } | |
| #endif | |
| return UserActionableError::kSignInNeedsUpdate; | |
| } | |
| if (last_actionable_error_.action == UPGRADE_CLIENT) { | |
| return UserActionableError::kNeedsClientUpgrade; | |
| } | |
| if (user_settings_->IsPassphraseRequiredForPreferredDataTypes()) { | |
| return UserActionableError::kNeedsPassphrase; | |
| } | |
| if (user_settings_->IsTrustedVaultKeyRequiredForPreferredDataTypes()) { | |
| return user_settings_->IsEncryptEverythingEnabled() | |
| ? UserActionableError::kNeedsTrustedVaultKeyForEverything | |
| : UserActionableError::kNeedsTrustedVaultKeyForPasswords; | |
| } | |
| #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) | |
| if (passkeys_locked_ && GetActiveDataTypes().Has(WEBAUTHN_CREDENTIAL)) { | |
| return UserActionableError::kPasskeysLocked; | |
| } | |
| #endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) | |
| if (user_settings_->IsTrustedVaultRecoverabilityDegraded()) { | |
| return user_settings_->IsEncryptEverythingEnabled() | |
| ? UserActionableError:: | |
| kTrustedVaultRecoverabilityDegradedForEverything | |
| : UserActionableError:: | |
| kTrustedVaultRecoverabilityDegradedForPasswords; | |
| } | |
| #if BUILDFLAG(IS_ANDROID) | |
| if (user_settings_->GetSelectedTypes().Has(UserSelectableType::kPasswords) && | |
| password_manager::IsGmsCoreUpdateRequired()) { | |
| return UserActionableError::kNeedsUPMBackendUpgrade; | |
| } | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| // This error should ideally be the last one to be checked. Any new identity | |
| // errors should be handled before this. | |
| const DataTypeStatusTable::TypeErrorMap data_type_errors = | |
| data_type_manager_->GetDataTypeErrors(); | |
| auto it = data_type_errors.find(BOOKMARKS); | |
| if (it != data_type_errors.end() && | |
| bookmark_sync_error_state_.IsActionableError(it->second)) { | |
| return UserActionableError::kBookmarksLimitExceeded; | |
| } | |
| return UserActionableError::kNone; | |
| } | |
| void SyncServiceImpl::NotifyObservers() { | |
| if (is_shutting_down_) { | |
| return; | |
| } | |
| CHECK(observers_); | |
| SyncService::UserActionableError user_actionable_error = | |
| GetUserActionableError(); | |
| // Exclude kNone bucket as it doesn't provide any useful information. The | |
| // metric is recorded at most once per browser / profile session. | |
| if (user_actionable_error != UserActionableError::kNone && | |
| encountered_user_actionable_errors_.insert(user_actionable_error) | |
| .second) { | |
| base::UmaHistogramEnumeration("Sync.UserActionableError", | |
| user_actionable_error); | |
| } | |
| for (SyncServiceObserver& observer : *observers_) { | |
| observer.OnStateChanged(this); | |
| // Shutdown() can be triggered re-entrantly while this notification loop is | |
| // already running. Shutdown destroys data type controllers and clears | |
| // `observers_`, so abort remaining iteration to avoid use-after-free. | |
| if (is_shutting_down_) { | |
| return; | |
| } | |
| } | |
| } | |
| void SyncServiceImpl::NotifySyncCycleCompleted() { | |
| if (is_shutting_down_) { | |
| return; | |
| } | |
| CHECK(observers_); | |
| for (SyncServiceObserver& observer : *observers_) { | |
| observer.OnSyncCycleCompleted(this); | |
| // Shutdown() can be triggered re-entrantly while this notification loop is | |
| // already running. Shutdown destroys data type controllers and clears | |
| // `observers_`, so abort remaining iteration to avoid use-after-free. | |
| if (is_shutting_down_) { | |
| return; | |
| } | |
| } | |
| } | |
| void SyncServiceImpl::NotifyShutdown() { | |
| CHECK(observers_); | |
| for (SyncServiceObserver& observer : *observers_) { | |
| observer.OnSyncShutdown(this); | |
| } | |
| } | |
| void SyncServiceImpl::ClearUnrecoverableError() { | |
| unrecoverable_error_reason_ = std::nullopt; | |
| unrecoverable_error_message_.clear(); | |
| unrecoverable_error_location_ = base::Location(); | |
| } | |
| void SyncServiceImpl::OnUnrecoverableErrorImpl( | |
| const base::Location& from_here, | |
| const std::string& message, | |
| UnrecoverableErrorReason reason) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| unrecoverable_error_reason_ = reason; | |
| unrecoverable_error_message_ = message; | |
| unrecoverable_error_location_ = from_here; | |
| LOG(ERROR) << "Unrecoverable error detected at " << from_here.ToString() | |
| << " -- SyncServiceImpl unusable: " << message; | |
| // Shut the Sync machinery down. The existence of | |
| // `unrecoverable_error_reason_` and thus `DISABLE_REASON_UNRECOVERABLE_ERROR` | |
| // will prevent Sync from starting up again (even in transport-only mode). | |
| ResetEngine(ResetEngineReason::kUnrecoverableError); | |
| } | |
| void SyncServiceImpl::DataTypePreconditionChanged(DataType type) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (!engine_ || !engine_->IsInitialized() || !data_type_manager_) { | |
| return; | |
| } | |
| data_type_manager_->DataTypePreconditionChanged(type); | |
| } | |
| void SyncServiceImpl::OnEngineInitialized(bool success, | |
| bool is_first_time_sync_configure) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| DCHECK(IsEngineAllowedToRun()); | |
| // The very first time the backend initializes is effectively the first time | |
| // we can say we successfully "synced". | |
| is_first_time_sync_configure_ = is_first_time_sync_configure; | |
| if (!success) { | |
| // Something went unexpectedly wrong. Play it safe: stop syncing at once | |
| // and surface error UI to alert the user sync has stopped. | |
| OnUnrecoverableErrorImpl(FROM_HERE, "BackendInitialize failure", | |
| ERROR_REASON_ENGINE_INIT_FAILURE); | |
| return; | |
| } | |
| if (!protocol_event_observers_.empty()) { | |
| engine_->RequestBufferedProtocolEventsAndEnableForwarding(); | |
| } | |
| data_type_manager_->SetConfigurer(engine_.get()); | |
| crypto_.SetSyncEngine(GetAccountInfo(), engine_.get()); | |
| std::vector<base::OnceClosure> tasks; | |
| tasks.swap(tasks_waiting_for_engine_initialization_); | |
| for (base::OnceClosure& task : tasks) { | |
| std::move(task).Run(); | |
| } | |
| sync_prefs_.MaybeMigratePrefsForSyncToSigninPart2( | |
| GetAccountInfo().gaia, user_settings_->IsUsingExplicitPassphrase()); | |
| // Cache trusted vault debug info into prefs, to make it synchronously | |
| // available upon future profile startups. | |
| CacheTrustedVaultDebugInfoToPrefsFromEngine(); | |
| // Check for a cookie jar mismatch. | |
| if (identity_manager_) { | |
| signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info = | |
| identity_manager_->GetAccountsInCookieJar(); | |
| if (accounts_in_cookie_jar_info.AreAccountsFresh()) { | |
| OnAccountsInCookieUpdated(accounts_in_cookie_jar_info, | |
| GoogleServiceAuthError::AuthErrorNone()); | |
| } | |
| } | |
| // Tentatively use a generic reason, but it may be later overridden | |
| // to CONFIGURE_REASON_NEW_CLIENT. The overriding code is needed | |
| // anyway in case sync setup is in progress and configuration cannot | |
| // be started right away. | |
| ConfigureDataTypeManager(ConfigureReason::kExistingClientRestart, | |
| /*bypass_setup_in_progress_check=*/false); | |
| } | |
| void SyncServiceImpl::OnSyncCycleCompleted(const SyncCycleSnapshot& snapshot) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK(engine_); | |
| CHECK(engine_->IsInitialized()); | |
| last_snapshot_ = snapshot; | |
| // Cache trusted vault debug info into prefs, to make it synchronously | |
| // available upon future profile startups. In most cases this will happen in | |
| // OnEngineInitialized(), but it may also happen that the information was just | |
| // populated server-side and downloaded, after (or long after) the engine is | |
| // initialized. | |
| CacheTrustedVaultDebugInfoToPrefsFromEngine(); | |
| DVLOG(2) << "Notifying observers sync cycle completed"; | |
| NotifySyncCycleCompleted(); | |
| } | |
| void SyncServiceImpl::OnConnectionStatusChange(ConnectionStatus status) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (!IsLocalSyncEnabled()) { | |
| auth_manager_->ConnectionStatusChanged(status); | |
| } | |
| DVLOG(2) << "Notify observers OnConnectionStatusChange"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::OnMigrationNeededForTypes(DataTypeSet types) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| DCHECK(engine_); | |
| DCHECK(engine_->IsInitialized()); | |
| // Migrator must be valid, because we don't sync until it is created and this | |
| // callback originates from a sync cycle. | |
| migrator_->MigrateTypes(types); | |
| } | |
| void SyncServiceImpl::OnActionableProtocolError( | |
| const SyncProtocolError& error) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| last_actionable_error_ = error; | |
| DCHECK_NE(last_actionable_error_.action, UNKNOWN_ACTION); | |
| switch (error.action) { | |
| case UPGRADE_CLIENT: | |
| if (IsSetupInProgress()) { | |
| StopAndClear(ResetEngineReason::kUpgradeClientError); | |
| } | |
| // Trigger an unrecoverable error to stop syncing. | |
| OnUnrecoverableErrorImpl(FROM_HERE, | |
| last_actionable_error_.error_description, | |
| ERROR_REASON_ACTIONABLE_ERROR); | |
| break; | |
| case DISABLE_SYNC_ON_CLIENT: | |
| if (error.error_type == NOT_MY_BIRTHDAY || | |
| error.error_type == ENCRYPTION_OBSOLETE) { | |
| // Note: For legacy reasons, `kImplicitPassphrase` is used to represent | |
| // the "unknown" state. | |
| base::UmaHistogramEnumeration( | |
| "Sync.PassphraseTypeUponNotMyBirthdayOrEncryptionObsolete", | |
| crypto_.GetPassphraseType().value_or( | |
| PassphraseType::kImplicitPassphrase)); | |
| // Account passphrase pref should be cleared when sync is reset from the | |
| // dashboard because then the cached passphrase wouldn't be useful | |
| // anymore. | |
| sync_prefs_.ClearEncryptionBootstrapTokenForAccount( | |
| GetAccountInfo().gaia); | |
| } | |
| // Security domain state might be reset, reset local state as well. | |
| sync_client_->GetTrustedVaultClient()->ClearLocalDataForAccount( | |
| GetAccountInfo()); | |
| // Note: This method might get called again in the following code when | |
| // clearing the primary account. But due to rarity of the event, this | |
| // should be okay. | |
| StopAndClear(ResetEngineReason::kDisableSyncOnClient); | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| // On Ash, the primary account is always set and sync the feature | |
| // turned on, so a dedicated bit is needed to ensure that | |
| // Sync-the-feature remains off. Note that sync-the-transport will restart | |
| // immediately because IsEngineAllowedToRun() is almost certainly true at | |
| // this point and StopAndClear() leads to TryStart(). | |
| user_settings_->SetSyncFeatureDisabledViaDashboard(); | |
| #else // !BUILDFLAG(IS_CHROMEOS) | |
| // On every platform except ash, revoke the Sync consent/Clear primary | |
| // account after a dashboard clear. | |
| // TODO(crbug.com/40066949): Simplify once kSync becomes unreachable or is | |
| // deleted from the codebase. See ConsentLevel::kSync documentation for | |
| // details. | |
| if (!IsLocalSyncEnabled() && | |
| identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) { | |
| signin::PrimaryAccountMutator* account_mutator = | |
| identity_manager_->GetPrimaryAccountMutator(); | |
| // GetPrimaryAccountMutator() returns nullptr on ChromeOS only. | |
| DCHECK(account_mutator); | |
| // TODO(crbug.com/40220945): make the behaviour consistent across | |
| // platforms. Any platforms which support a single-step flow that signs | |
| // in and enables sync should clear the primary account here for | |
| // symmetry. | |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | |
| // On mobile, fully sign out the user (clear the primary account) but | |
| // do not remove the list of known accounts, as the user may sign in | |
| // again. | |
| account_mutator->RemovePrimaryAccountButKeepTokens( | |
| signin_metrics::ProfileSignout::kServerForcedDisable); | |
| #else | |
| // The Sync consent will be revoked, and Sync will enter | |
| // Sync-the-transport mode. | |
| CHECK(!GetAccountInfo().gaia.empty()); | |
| // Migrating the user's selected types prefs to ensure that they will | |
| // be available in Sync-the-transport mode. | |
| SyncPrefs::MigrateGlobalDataTypePrefsToAccount( | |
| sync_client_->GetPrefService(), GetAccountInfo().gaia); | |
| // Note: On some platforms, revoking the sync consent will also clear | |
| // the primary account as transitioning from ConsentLevel::kSync to | |
| // ConsentLevel::kSignin is not supported. | |
| account_mutator->RevokeSyncConsent( | |
| signin_metrics::ProfileSignout::kServerForcedDisable); | |
| #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | |
| } | |
| #endif // BUILDFLAG(IS_CHROMEOS) | |
| break; | |
| case STOP_SYNC_FOR_DISABLED_ACCOUNT: | |
| // Sync disabled by domain admin. Stop syncing until next restart. | |
| sync_disabled_by_admin_ = true; | |
| ResetEngine(ResetEngineReason::kDisabledAccount); | |
| break; | |
| case RESET_LOCAL_SYNC_DATA: | |
| ResetEngine(ResetEngineReason::kResetLocalData); | |
| break; | |
| case UNKNOWN_ACTION: | |
| NOTREACHED(); | |
| } | |
| DVLOG(2) << "Notify observers OnActionableProtocolError"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::OnBackedOffTypesChanged() { | |
| DVLOG(2) << "Notify observers OnBackedOffTypesChanged"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::OnInvalidationStatusChanged() { | |
| DVLOG(2) << "Notify observers OnInvalidationStatusChanged"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::OnNewInvalidatedDataTypes() { | |
| DVLOG(2) << "Notify observers OnNewInvalidatedDataTypes"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::FetchAccessToken( | |
| base::OnceCallback<void(signin::AccessTokenInfo)> callback) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Local sync communicates with a loopback server on disk and does not use | |
| // access tokens. | |
| CHECK(!IsLocalSyncEnabled()); | |
| if (!auth_manager_) { | |
| std::move(callback).Run(signin::AccessTokenInfo()); | |
| return; | |
| } | |
| auth_manager_->FetchAccessToken(std::move(callback)); | |
| } | |
| void SyncServiceImpl::OnConfigureDone( | |
| const DataTypeManager::ConfigureResult& result) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| DVLOG(1) << "SyncServiceImpl::OnConfigureDone called with status: " | |
| << result.status; | |
| switch (result.status) { | |
| case DataTypeManager::ABORTED: | |
| // Configuration was aborted. This is not an error, if initiated by user. | |
| DVLOG(0) << "SyncServiceImpl sync configuration aborted"; | |
| return; | |
| case DataTypeManager::OK: | |
| // Some or all types succeeded. | |
| break; | |
| } | |
| base::UmaHistogramBoolean("Sync.ConfigureDataTypeManager.Finished", | |
| is_first_time_sync_configure_); | |
| // We should never get in a state where we have no encrypted datatypes | |
| // enabled, and yet we still think we require a passphrase for decryption. | |
| DCHECK(!user_settings_->IsPassphraseRequiredForPreferredDataTypes() || | |
| user_settings_->IsEncryptedDatatypePreferred()); | |
| DVLOG(2) << "Notify observers OnConfigureDone"; | |
| NotifyObservers(); | |
| // Update configured data types and start handling incoming invalidations. The | |
| // order is important to guarantee that data types are configured to prevent | |
| // filtering out invalidations. | |
| UpdateDataTypesForInvalidations(); | |
| engine_->StartHandlingInvalidations(); | |
| if (migrator_.get() && migrator_->state() != BackendMigrator::IDLE) { | |
| // Migration in progress. Let the migrator know we just finished | |
| // configuring something. It will be up to the migrator to call | |
| // StartSyncingWithServer() if migration is now finished. | |
| migrator_->OnConfigureDone(result); | |
| return; | |
| } | |
| StartSyncingWithServer(); | |
| } | |
| void SyncServiceImpl::OnConfigureStart() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| engine_->StartConfiguration(); | |
| DVLOG(2) << "Notify observers OnConfigureStart"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::SyncAuthAccountStateChanged() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (!IsSignedIn()) { | |
| // The account was signed out, so shut down. | |
| sync_disabled_by_admin_ = false; | |
| StopAndClear(ResetEngineReason::kNotSignedIn); | |
| DCHECK(!engine_); | |
| } else { | |
| // Either a new account was signed in, or the existing account's | |
| // `is_sync_consented` and/or `managed_status` have changed. Start up or | |
| // reconfigure. | |
| if (!engine_) { | |
| // If sync startup is still deferred, then honor that. (In practice, this | |
| // mostly happens when the `managed_status` of the account gets | |
| // determined.) | |
| if (deferring_first_start_since_.is_null()) { | |
| TryStart(); | |
| } | |
| NotifyObservers(); | |
| } else { | |
| ConfigureDataTypeManager(ConfigureReason::kReconfiguration, | |
| /*bypass_setup_in_progress_check=*/false); | |
| } | |
| } | |
| } | |
| void SyncServiceImpl::SyncAuthCredentialsChanged() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Cache in prefs whether a persistent auth error exists. | |
| if (auth_manager_->IsSyncPaused()) { | |
| sync_prefs_.SetHasCachedPersistentAuthErrorForMetrics(true); | |
| } else if (!auth_manager_->GetCredentials().access_token_info.token.empty()) { | |
| if (!IsSyncFeatureEnabled() && | |
| sync_prefs_.HasCachedPersistentAuthErrorForMetrics()) { | |
| // An auth error is being fixed while in transport mode. Record the amount | |
| // of unsynced data in histograms. | |
| GetTypesWithUnsyncedData( | |
| TypesRequiringUnsyncedDataCheckOnSignout(), | |
| base::BindOnce( | |
| &SyncRecordDataTypeNumUnsyncedEntitiesFromDataCounts, | |
| UnsyncedDataRecordingEvent::kOnReauthFromPendingState)); | |
| } | |
| // In order to conclude with certainty that there is no persistent auth | |
| // error, it is necessary to get an access token successfully. | |
| sync_prefs_.SetHasCachedPersistentAuthErrorForMetrics(false); | |
| } | |
| // If the engine isn't allowed to start anymore due to the credentials change, | |
| // then shut down. This happens when there is a persistent auth error (e.g. | |
| // the user signs out on the web), which implies the "Sync paused" state. | |
| if (!IsEngineAllowedToRun()) { | |
| // If the engine currently exists, then ResetEngine() will notify observers | |
| // anyway. Otherwise, notify them here. (One relevant case is when entering | |
| // the PAUSED state before the engine was created, e.g. during deferred | |
| // startup.) | |
| if (!engine_) { | |
| DVLOG(2) << "Notify observers on credentials changed"; | |
| NotifyObservers(); | |
| } | |
| ResetEngine(ResetEngineReason::kCredentialsChanged); | |
| return; | |
| } | |
| if (!engine_) { | |
| TryStart(); | |
| } else if (!base::FeatureList::IsEnabled(kSyncUsePropagatedAccessToken)) { | |
| // When kSyncUsePropagatedAccessToken is enabled, access tokens are fetched | |
| // on demand and propagated via SyncCycle when needed rather than cached in | |
| // the network sync layer. | |
| // If the engine already exists, just propagate the new credentials. | |
| SyncCredentials credentials = auth_manager_->GetCredentials(); | |
| if (credentials.access_token_info.token.empty()) { | |
| engine_->InvalidateCredentials(); | |
| } else { | |
| engine_->UpdateCredentials(credentials); | |
| } | |
| } else if (!auth_manager_->GetCredentials().access_token_info.token.empty()) { | |
| // If a sync cycle previously failed due to an auth error (HTTP 401), the | |
| // scheduler entered exponential backoff. Notifying the engine here allows | |
| // it to exit backoff and immediately retry sync once new credentials | |
| // are available. | |
| engine_->OnCredentialsChanged(); | |
| } | |
| DVLOG(2) << "Notify observers on credentials changed"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::CryptoStateChanged() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| DVLOG(2) << "Notify observers on CryptoStateChanged"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::CryptoRequiredUserActionChanged() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| MaybeRecordTrustedVaultHistograms(); | |
| } | |
| void SyncServiceImpl::MaybeRecordTrustedVaultHistograms() { | |
| if (should_record_trusted_vault_error_shown_on_startup_ && | |
| crypto_.IsTrustedVaultKeyRequiredStateKnown() && | |
| user_settings_->IsEncryptedDatatypePreferred()) { | |
| // If the key-required state is known, the engine must exist. | |
| DCHECK(engine_); | |
| should_record_trusted_vault_error_shown_on_startup_ = false; | |
| if (crypto_.GetPassphraseType() == | |
| PassphraseType::kTrustedVaultPassphrase) { | |
| RecordTrustedVaultHistogramBooleanWithMigrationSuffix( | |
| "Sync.TrustedVaultErrorShownOnStartup", | |
| user_settings_->IsTrustedVaultKeyRequiredForPreferredDataTypes(), | |
| engine_->GetDetailedStatus()); | |
| if (is_first_time_sync_configure_) { | |
| // A 'first time sync configure' is an indication that the account was | |
| // added to the browser recently (sign in). | |
| base::UmaHistogramBoolean( | |
| "Sync.TrustedVaultErrorShownOnFirstTimeSync2", | |
| user_settings_->IsTrustedVaultKeyRequiredForPreferredDataTypes()); | |
| } | |
| } | |
| } | |
| } | |
| void SyncServiceImpl::ReconfigureDataTypesDueToCrypto() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| ConfigureDataTypeManager(ConfigureReason::kCrypto, | |
| /*bypass_setup_in_progress_check=*/false); | |
| } | |
| void SyncServiceImpl::PassphraseTypeChanged(PassphraseType passphrase_type) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | |
| // If kReplaceSyncPromosWithSignInPromos is enabled, new users with custom | |
| // passphrase should have kAutofill disabled upon the initial sign-in. This is | |
| // done to prevent confusion, as addresses are NOT encrypted by the custom | |
| // passphrase | |
| // | |
| // This check is skipped on desktop (Windows, Mac, Linux) and ChromeOS because | |
| // the user interface on those platforms already clarifies this nuance about | |
| // address encryption. | |
| // | |
| // The first `PassphraseTypeChanged()` call reflects the server-side | |
| // passphrase type before signing in. | |
| if (!sync_prefs_.GetCachedPassphraseType().has_value() && | |
| IsExplicitPassphrase(passphrase_type) && | |
| GetSyncAccountStateForPrefs() == | |
| SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent && | |
| sync_prefs_.DoesTypeHaveDefaultValueForAccount( | |
| UserSelectableType::kAutofill, GetAccountInfo().gaia) && | |
| IsReplaceSyncPromosWithSignInPromosEnabled()) { | |
| GetUserSettings()->SetSelectedType(UserSelectableType::kAutofill, false); | |
| } | |
| #endif | |
| sync_prefs_.SetCachedPassphraseType(passphrase_type); | |
| } | |
| std::optional<PassphraseType> SyncServiceImpl::GetPassphraseType() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return sync_prefs_.GetCachedPassphraseType(); | |
| } | |
| void SyncServiceImpl::SetEncryptionBootstrapToken( | |
| const CustomPassphraseBootstrapToken& bootstrap_token, | |
| const os_crypt_async::Encryptor& encryptor) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| user_settings_->SetEncryptionBootstrapToken(bootstrap_token, encryptor); | |
| SendExplicitPassphraseToPlatformClient(); | |
| } | |
| CustomPassphraseBootstrapToken SyncServiceImpl::GetEncryptionBootstrapToken( | |
| const os_crypt_async::Encryptor& encryptor) const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return user_settings_->GetEncryptionBootstrapToken(encryptor); | |
| } | |
| bool SyncServiceImpl::IsCustomPassphraseAllowed() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return sync_client_->IsCustomPassphraseAllowed(); | |
| } | |
| SyncPrefs::SyncAccountState SyncServiceImpl::GetSyncAccountStateForPrefs() | |
| const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (IsLocalSyncEnabled()) { | |
| // Local sync should behave like a syncing user. | |
| return SyncPrefs::SyncAccountState::kSyncing; | |
| } | |
| if (!IsSignedIn()) { | |
| return SyncPrefs::SyncAccountState::kNotSignedIn; | |
| } | |
| // This doesn't check IsSyncFeatureEnabled() so it covers the case of advanced | |
| // sync setup, where IsInitialSyncFeatureSetupComplete() is not true yet. | |
| return HasSyncConsent() | |
| ? SyncPrefs::SyncAccountState::kSyncing | |
| : SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent; | |
| } | |
| CoreAccountInfo SyncServiceImpl::GetSyncAccountInfoForPrefs() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return GetAccountInfo(); | |
| } | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| void SyncServiceImpl::OnSyncFeatureDisabledViaDashboardCleared() { | |
| // If the Sync engine was already initialized (probably running in transport | |
| // mode), just reconfigure. | |
| if (engine_ && engine_->IsInitialized()) { | |
| ConfigureDataTypeManager(ConfigureReason::kReconfiguration, | |
| /*bypass_setup_in_progress_check=*/false); | |
| } else { | |
| // Otherwise try to start up. Note that there might still be other disable | |
| // reasons remaining, in which case this will effectively do nothing. | |
| TryStart(); | |
| } | |
| NotifyObservers(); | |
| } | |
| #endif // BUILDFLAG(IS_CHROMEOS) | |
| bool SyncServiceImpl::IsSetupInProgress() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return outstanding_setup_in_progress_handles_ > 0; | |
| } | |
| bool SyncServiceImpl::QueryDetailedSyncStatusForDebugging( | |
| SyncStatus* result) const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (engine_ && engine_->IsInitialized()) { | |
| *result = engine_->GetDetailedStatus(); | |
| return true; | |
| } | |
| SyncStatus status; | |
| status.sync_protocol_error = last_actionable_error_; | |
| *result = status; | |
| return false; | |
| } | |
| GoogleServiceAuthError SyncServiceImpl::GetAuthError() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return auth_manager_->GetLastAuthError(); | |
| } | |
| base::Time SyncServiceImpl::GetAuthErrorTime() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return auth_manager_->GetLastAuthErrorTime(); | |
| } | |
| bool SyncServiceImpl::HasCachedPersistentAuthErrorForMetrics() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return sync_prefs_.HasCachedPersistentAuthErrorForMetrics(); | |
| } | |
| std::unique_ptr<SyncSetupInProgressHandle> | |
| SyncServiceImpl::GetSetupInProgressHandle() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (++outstanding_setup_in_progress_handles_ == 1) { | |
| TryStart(); | |
| DVLOG(2) << "Notify observers GetSetupInProgressHandle"; | |
| NotifyObservers(); | |
| } | |
| return std::make_unique<SyncSetupInProgressHandle>( | |
| base::BindRepeating(&SyncServiceImpl::OnSetupInProgressHandleDestroyed, | |
| weak_factory_.GetWeakPtr())); | |
| } | |
| bool SyncServiceImpl::IsLocalSyncEnabled() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return sync_prefs_.IsLocalSyncEnabled(); | |
| } | |
| void SyncServiceImpl::TriggerRefresh(TriggerRefreshSource source, | |
| const DataTypeSet& types) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| base::UmaHistogramEnumeration("Sync.TriggerRefreshSource", source); | |
| if (engine_ && engine_->IsInitialized()) { | |
| engine_->TriggerRefresh(types); | |
| } | |
| } | |
| bool SyncServiceImpl::IsSignedIn() const { | |
| // Sync is logged in if there is a non-empty account id. | |
| return !GetAccountInfo().account_id.empty(); | |
| } | |
| base::Time SyncServiceImpl::GetLastSyncedTimeForDebugging() const { | |
| if (!engine_ || !engine_->IsInitialized()) { | |
| return base::Time(); | |
| } | |
| return engine_->GetLastSyncedTimeForDebugging(); | |
| } | |
| void SyncServiceImpl::OnSelectedTypesChanged() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| ConfigureDataTypeManager(ConfigureReason::kReconfiguration, | |
| /*bypass_setup_in_progress_check=*/false); | |
| } | |
| SyncClient* SyncServiceImpl::GetSyncClientForTest() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| return sync_client_.get(); | |
| } | |
| void SyncServiceImpl::ReportDataTypeErrorForTest(DataType type) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| DataTypeController* controller = | |
| data_type_manager_->GetControllerForTest(type); // IN-TEST | |
| CHECK(controller); | |
| controller->ReportBridgeErrorForTest(); // IN-TEST | |
| } | |
| void SyncServiceImpl::RunOrQueueTaskOnEngineInitializedForTest( | |
| base::OnceClosure task) { | |
| RunOrQueueTaskOnEngineInitialized(std::move(task)); | |
| } | |
| void SyncServiceImpl::AddObserver(SyncServiceObserver* observer) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK(observers_); | |
| observers_->AddObserver(observer); | |
| } | |
| void SyncServiceImpl::RemoveObserver(SyncServiceObserver* observer) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK(observers_); | |
| observers_->RemoveObserver(observer); | |
| } | |
| bool SyncServiceImpl::HasObserver(const SyncServiceObserver* observer) const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK(observers_); | |
| return observers_->HasObserver(observer); | |
| } | |
| DataTypeSet SyncServiceImpl::GetPreferredDataTypes() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Some questionable callers exercise this function after Shutdown(). The | |
| // semantics aren't clear, but for cases like GetUploadToGoogleState() a | |
| // sensible behavior is to return an empty set. | |
| if (!data_type_manager_) { | |
| return DataTypeSet(); | |
| } | |
| DataTypeSet types = user_settings_->GetPreferredDataTypes(); | |
| // SyncUserSettings already filters out UserSelectableTypes that aren't | |
| // supported in transport mode. However, there are two reasons why the | |
| // DataTypes still need to be filtered here: | |
| // 1) For some UserSelectableTypes, some of their DataTypes are supported | |
| // while others aren't. | |
| // 2) Some DataTypes implement additional preconditions in | |
| // ShouldRunInTransportOnlyMode() (e.g. related to passphrase type). | |
| if (UseTransportOnlyMode()) { | |
| types = Intersection( | |
| types, data_type_manager_->GetDataTypesForTransportOnlyMode()); | |
| } | |
| return types; | |
| } | |
| DataTypeSet SyncServiceImpl::GetDataTypesForTransportOnlyMode() const { | |
| if (!data_type_manager_) { | |
| return DataTypeSet(); | |
| } | |
| return data_type_manager_->GetDataTypesForTransportOnlyMode(); | |
| } | |
| DataTypeSet SyncServiceImpl::GetActiveDataTypes() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Return an empty set unless the TransportState is ACTIVE. (There may in fact | |
| // also be active types while it is CONFIGURING, but those are not exposed to | |
| // clients, mostly for historic reasons.) | |
| if (GetTransportState() != TransportState::ACTIVE) { | |
| return DataTypeSet(); | |
| } | |
| CHECK(data_type_manager_); | |
| return data_type_manager_->GetActiveDataTypes(); | |
| } | |
| DataTypeSet SyncServiceImpl::GetTypesWithPendingDownloadForInitialSync() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK(data_type_manager_); | |
| if (GetTransportState() == TransportState::INITIALIZING && | |
| !sync_client_->GetSyncEngineFactory()->HasTransportDataIncludingFirstSync( | |
| signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia))) { | |
| // The engine is initializing for the very first sync (usually after | |
| // sign-in). In this case all types are reported as pending download, | |
| // optimistically assuming datatype preconditions will be met. | |
| return GetPreferredDataTypes(); | |
| } | |
| return data_type_manager_->GetTypesWithPendingDownloadForInitialSync(); | |
| } | |
| void SyncServiceImpl::ConfigureDataTypeManager( | |
| ConfigureReason reason, | |
| bool bypass_setup_in_progress_check) { | |
| // Don't configure datatypes if the setup UI is still on the screen - this | |
| // is to help multi-screen setting UIs (like iOS) where they don't want to | |
| // start syncing data until the user is done configuring encryption options, | |
| // etc. ConfigureDatatypeManager() will get called again once the last | |
| // SyncSetupInProgressHandle is released. | |
| if (!engine_ || !engine_->IsInitialized() || | |
| (!bypass_setup_in_progress_check && IsSetupInProgress())) { | |
| // In any case, notify the observers. Whatever triggered the reconfigure | |
| // (attempt) might be interesting to them. | |
| NotifyObservers(); | |
| return; | |
| } | |
| // If the account type hasn't been determined yet, don't configure. A | |
| // configuration will be triggered again once the type has been determined. | |
| if (!IsLocalSyncEnabled() && | |
| auth_manager_->GetActiveAccountInfo().managed_status == | |
| signin::AccountManagedStatusFinderOutcome::kPending) { | |
| return; | |
| } | |
| DCHECK(!engine_->GetCacheGuid().empty()); | |
| DVLOG(1) << "Started DataTypeManager configuration, reason: " | |
| << static_cast<int>(reason); | |
| const bool use_transport_only_mode = UseTransportOnlyMode(); | |
| ConfigureContext configure_context; | |
| configure_context.authenticated_gaia_id = GetAccountInfo().gaia; | |
| configure_context.account_managed_status = | |
| auth_manager_->GetActiveAccountInfo().managed_status; | |
| configure_context.cache_guid = engine_->GetCacheGuid(); | |
| configure_context.sync_mode = | |
| use_transport_only_mode ? SyncMode::kTransportOnly : SyncMode::kFull; | |
| configure_context.reason = reason; | |
| configure_context.configuration_start_time = base::Time::Now(); | |
| DCHECK(!configure_context.cache_guid.empty()); | |
| if (!migrator_) { | |
| // We create the migrator at the same time. | |
| migrator_ = std::make_unique<BackendMigrator>( | |
| debug_identifier_, data_type_manager_.get(), | |
| base::BindRepeating(&SyncServiceImpl::ConfigureDataTypeManager, | |
| base::Unretained(this), ConfigureReason::kMigration, | |
| /*bypass_setup_in_progress_check=*/true), | |
| base::BindRepeating(&SyncServiceImpl::StartSyncingWithServer, | |
| base::Unretained(this))); | |
| // Override reason if no configuration has completed ever. | |
| if (is_first_time_sync_configure_) { | |
| configure_context.reason = ConfigureReason::kNewClient; | |
| } | |
| } | |
| DCHECK(!configure_context.authenticated_gaia_id.empty() || | |
| IsLocalSyncEnabled()); | |
| DCHECK(!configure_context.cache_guid.empty()); | |
| DCHECK_NE(configure_context.reason, ConfigureReason::kUnknown); | |
| data_type_manager_->Configure(GetPreferredDataTypes(), configure_context); | |
| // Record in UMA whether we're configuring the full Sync feature or only the | |
| // transport. These values are persisted to logs. Entries should not be | |
| // renumbered and numeric values should never be reused. Keep in sync with | |
| // SyncFeatureOrTransport in tools/metrics/histograms/metadata/sync/enums.xml. | |
| // LINT.IfChange(SyncFeatureOrTransport) | |
| enum class ConfigureDataTypeManagerOption { | |
| kFeature = 0, | |
| kTransport = 1, | |
| kMaxValue = kTransport | |
| }; | |
| // LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncFeatureOrTransport) | |
| base::UmaHistogramEnumeration("Sync.ConfigureDataTypeManagerOption", | |
| use_transport_only_mode | |
| ? ConfigureDataTypeManagerOption::kTransport | |
| : ConfigureDataTypeManagerOption::kFeature); | |
| base::UmaHistogramEnumeration("Sync.ConfigureDataTypeManagerReason", | |
| configure_context.reason); | |
| base::UmaHistogramBoolean("Sync.ConfigureDataTypeManager.Start", | |
| is_first_time_sync_configure_); | |
| // Record the user's choice of data types - in different ways depending on | |
| // whether Sync-the-feature is enabled (which uses "SyncEverything") or not | |
| // (which doesn't). | |
| if (use_transport_only_mode) { | |
| for (UserSelectableType type : user_settings_->GetSelectedTypes()) { | |
| DataTypeForHistograms canonical_data_type = | |
| DataTypeHistogramValue(UserSelectableTypeToCanonicalDataType(type)); | |
| base::UmaHistogramEnumeration("Sync.SelectedTypesInTransportMode", | |
| canonical_data_type); | |
| } | |
| } else { | |
| bool sync_everything = user_settings_->IsSyncEverythingEnabled(); | |
| base::UmaHistogramBoolean("Sync.SyncEverything2", sync_everything); | |
| if (!sync_everything) { | |
| for (UserSelectableType type : user_settings_->GetSelectedTypes()) { | |
| DataTypeForHistograms canonical_data_type = | |
| DataTypeHistogramValue(UserSelectableTypeToCanonicalDataType(type)); | |
| base::UmaHistogramEnumeration("Sync.CustomSync3", canonical_data_type); | |
| } | |
| } | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| bool sync_everything_os = user_settings_->IsSyncAllOsTypesEnabled(); | |
| base::UmaHistogramBoolean("Sync.SyncEverythingOS", sync_everything_os); | |
| if (!sync_everything_os) { | |
| for (UserSelectableOsType type : user_settings_->GetSelectedOsTypes()) { | |
| DataTypeForHistograms canonical_data_type = DataTypeHistogramValue( | |
| UserSelectableOsTypeToCanonicalDataType(type)); | |
| base::UmaHistogramEnumeration("Sync.CustomOSSync", canonical_data_type); | |
| } | |
| } | |
| #endif // BUILDFLAG(IS_CHROMEOS) | |
| } | |
| NotifyObservers(); | |
| } | |
| bool SyncServiceImpl::UseTransportOnlyMode() const { | |
| // Note: When local Sync is enabled, then we want full-sync mode (not just | |
| // transport), even though Sync-the-feature is not considered enabled. | |
| return !IsSyncFeatureEnabled() && !IsLocalSyncEnabled(); | |
| } | |
| void SyncServiceImpl::UpdateDataTypesForInvalidations() { | |
| // Wait for configuring data types. This is needed to consider proxy types | |
| // which become known during configuration. | |
| if (!data_type_manager_ || | |
| data_type_manager_->state() != DataTypeManager::CONFIGURED) { | |
| return; | |
| } | |
| // No need to register invalidations for non-protocol or commit-only types. | |
| DataTypeSet types = Intersection(GetPreferredDataTypes(), ProtocolTypes()); | |
| types.RemoveAll(CommitOnlyTypes()); | |
| #if BUILDFLAG(IS_ANDROID) | |
| if (!sessions_invalidations_enabled_ && | |
| !base::FeatureList::IsEnabled( | |
| kAlwaysRegisterSessionsInvalidationsAndroid)) { | |
| types.Remove(SESSIONS); | |
| } | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| // Unsubscribe from data types with permanent errors. Types which are | |
| // unready or have crypto errors are intentionally kept because they may | |
| // change their state. | |
| types.RemoveAll(data_type_manager_->GetDataTypesWithPermanentErrors()); | |
| #if BUILDFLAG(IS_ANDROID) | |
| // On Android, don't subscribe to HISTORY invalidations, to save network | |
| // traffic. | |
| types.Remove(HISTORY); | |
| #endif | |
| types.RemoveAll(data_type_manager_->GetActiveProxyDataTypes()); | |
| sync_client_->GetSyncInvalidationsService()->SetInterestedDataTypes(types); | |
| } | |
| SyncCycleSnapshot SyncServiceImpl::GetLastCycleSnapshotForDebugging() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return last_snapshot_; | |
| } | |
| void SyncServiceImpl::HasUnsyncedItemsForTest( | |
| base::OnceCallback<void(bool)> cb) const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| DCHECK(engine_); | |
| DCHECK(engine_->IsInitialized()); | |
| engine_->HasUnsyncedItemsForTest(std::move(cb)); // IN-TEST | |
| } | |
| TypeStatusMapForDebugging SyncServiceImpl::GetTypeStatusMapForDebugging() | |
| const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (!engine_ || !engine_->IsInitialized()) { | |
| return TypeStatusMapForDebugging(); | |
| } | |
| const SyncStatus& detailed_status = engine_->GetDetailedStatus(); | |
| return data_type_manager_->GetTypeStatusMapForDebugging( | |
| detailed_status.throttled_types, detailed_status.backed_off_types); | |
| } | |
| void SyncServiceImpl::GetEntityCountsForDebugging( | |
| base::RepeatingCallback<void(const TypeEntitiesCount&)> callback) const { | |
| return data_type_manager_->GetEntityCountsForDebugging(std::move(callback)); | |
| } | |
| void SyncServiceImpl::OnSyncClientDisabledByPolicyChanged() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Local sync is not controlled by the "sync managed" policy, so these pref | |
| // changes make no difference to the service state. | |
| if (IsLocalSyncEnabled()) { | |
| return; | |
| } | |
| if (user_settings_->IsSyncClientDisabledByPolicy()) { | |
| StopAndClear(ResetEngineReason::kEnterprisePolicy); | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| // Disable OS data types to avoid automatic local data upload upon policy | |
| // removal, as OS data types do not support dual storage with UNO. | |
| if (!HasSyncConsent() && IsReplaceSyncPromosWithSignInPromosEnabled()) { | |
| user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/false, | |
| UserSelectableOsTypeSet()); | |
| } else { | |
| // On ChromeOS Ash, sync-the-feature stays disabled even after the policy | |
| // is removed, for historic reasons. It is unclear if this behavior is | |
| // optional, because it is indistinguishable from the | |
| // sync-reset-via-dashboard case. It can be resolved by invoking | |
| // ClearSyncFeatureDisabledViaDashboard(). | |
| user_settings_->SetSyncFeatureDisabledViaDashboard(); | |
| } | |
| #endif // BUILDFLAG(IS_CHROMEOS) | |
| } else { | |
| // Sync is no longer disabled by policy. Try starting it up if appropriate. | |
| DCHECK(!engine_); | |
| TryStart(); | |
| DVLOG(2) << "Notify observers OnSyncManagedPrefChange"; | |
| NotifyObservers(); | |
| } | |
| } | |
| #if !BUILDFLAG(IS_CHROMEOS) | |
| void SyncServiceImpl::OnInitialSyncFeatureSetupCompleted() { | |
| ConfigureDataTypeManager(ConfigureReason::kReconfiguration, | |
| /*bypass_setup_in_progress_check=*/false); | |
| } | |
| #endif // !BUILDFLAG(IS_CHROMEOS) | |
| void SyncServiceImpl::OnAccountsCookieDeletedByUserAction() { | |
| // Pass an empty `signin::AccountsInCookieJarInfo` to simulate empty cookies. | |
| MaybeClearAccountKeyedPreferences( | |
| identity_manager_, signin::AccountsInCookieJarInfo(), *user_settings_); | |
| } | |
| void SyncServiceImpl::OnAccountsInCookieUpdated( | |
| const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, | |
| const GoogleServiceAuthError& error) { | |
| OnAccountsInCookieUpdatedWithCallback(accounts_in_cookie_jar_info, | |
| base::NullCallback()); | |
| } | |
| void SyncServiceImpl::OnPrimaryAccountChanged( | |
| const signin::PrimaryAccountChangeEvent& event_details) { | |
| // When setting the primary account (at either ConsentLevel), record metrics. | |
| for (signin::ConsentLevel consent_level : | |
| {signin::ConsentLevel::kSignin, signin::ConsentLevel::kSync}) { | |
| switch (event_details.GetEventTypeFor(consent_level)) { | |
| case signin::PrimaryAccountChangeEvent::Type::kNone: | |
| case signin::PrimaryAccountChangeEvent::Type::kCleared: | |
| break; | |
| case signin::PrimaryAccountChangeEvent::Type::kSet: | |
| CHECK(event_details.GetSetPrimaryAccountAccessPoint().has_value()); | |
| signin_metrics::AccessPoint access_point = | |
| event_details.GetSetPrimaryAccountAccessPoint().value(); | |
| // The history opt-in state can only be queried after sync's internal | |
| // account state has been updated. That may or may not have happened | |
| // yet; depends on the order of IdentityManager observers | |
| // (SyncServiceImpl vs SyncAuthManager). | |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( | |
| FROM_HERE, | |
| base::BindOnce( | |
| &SyncServiceImpl::RecordHistoryOptInStateOnSigninHistograms, | |
| weak_factory_.GetWeakPtr(), access_point, consent_level)); | |
| } | |
| } | |
| if (event_details.GetEventTypeFor(signin::ConsentLevel::kSignin) == | |
| signin::PrimaryAccountChangeEvent::Type::kCleared) { | |
| MaybeClearAccountKeyedPreferences( | |
| identity_manager_, identity_manager_->GetAccountsInCookieJar(), | |
| *user_settings_); | |
| } | |
| } | |
| void SyncServiceImpl::OnIdentityManagerShutdown( | |
| signin::IdentityManager* identity_manager) { | |
| // Needs to be shutdown before IdentityManager. | |
| NOTREACHED(); | |
| } | |
| bool SyncServiceImpl::IsDeviceStatisticsMetricReportingEnabled() { | |
| return sync_client_->IsMetricsAndCrashReportingEnabled(); | |
| } | |
| std::unique_ptr<DeviceStatisticsRequest> | |
| SyncServiceImpl::CreateDeviceStatisticsRequest(const CoreAccountInfo& account, | |
| const GURL& url) { | |
| return std::make_unique<DeviceStatisticsRequestImpl>( | |
| sync_client_->GetIdentityManager(), url_loader_factory_, | |
| MakeUserAgentForSync(channel_), account, url); | |
| } | |
| base::flat_set<std::string> | |
| SyncServiceImpl::GetCurrentDeviceCacheGuidsForAllGaiaIds() const { | |
| return SyncTransportDataPrefs::GetCurrentDeviceCacheGuidsForAllGaiaIds( | |
| sync_client_->GetPrefService()); | |
| } | |
| base::flat_set<std::string> | |
| SyncServiceImpl::GetCurrentDeviceCacheGuidsForDeviceStatistics() { | |
| return GetCurrentDeviceCacheGuidsForAllGaiaIds(); | |
| } | |
| void SyncServiceImpl::OnAccountsInCookieUpdatedWithCallback( | |
| const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, | |
| base::OnceClosure callback) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| MaybeClearAccountKeyedPreferences( | |
| identity_manager_, accounts_in_cookie_jar_info, *user_settings_); | |
| if (!engine_ || !engine_->IsInitialized()) { | |
| return; | |
| } | |
| const bool cookie_jar_mismatch = HasCookieJarMismatch( | |
| accounts_in_cookie_jar_info.GetPotentiallyInvalidSignedInAccounts()); | |
| const bool cookie_jar_empty = | |
| accounts_in_cookie_jar_info.GetPotentiallyInvalidSignedInAccounts() | |
| .empty(); | |
| DVLOG(1) << "Cookie jar mismatch: " << cookie_jar_mismatch; | |
| DVLOG(1) << "Cookie jar empty: " << cookie_jar_empty; | |
| engine_->OnCookieJarChanged(cookie_jar_mismatch, std::move(callback)); | |
| } | |
| bool SyncServiceImpl::HasCookieJarMismatch( | |
| const std::vector<gaia::ListedAccount>& cookie_jar_accounts) { | |
| CoreAccountId account_id = GetAccountInfo().account_id; | |
| // Iterate through list of accounts, looking for current sync account. | |
| for (const gaia::ListedAccount& account : cookie_jar_accounts) { | |
| if (account.id == account_id) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void SyncServiceImpl::AddProtocolEventObserver( | |
| ProtocolEventObserver* observer) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| protocol_event_observers_.AddObserver(observer); | |
| if (engine_) { | |
| engine_->RequestBufferedProtocolEventsAndEnableForwarding(); | |
| } | |
| } | |
| void SyncServiceImpl::RemoveProtocolEventObserver( | |
| ProtocolEventObserver* observer) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| protocol_event_observers_.RemoveObserver(observer); | |
| if (engine_ && protocol_event_observers_.empty()) { | |
| engine_->DisableProtocolEventForwarding(); | |
| } | |
| } | |
| void SyncServiceImpl::GetAllNodesForDebugging( | |
| base::OnceCallback<void(base::ListValue)> callback) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| data_type_manager_->GetAllNodesForDebugging(std::move(callback)); | |
| } | |
| SyncService::DataTypeDownloadStatus SyncServiceImpl::GetDownloadStatusFor( | |
| DataType type) const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Download status doesn't make sense for non-real data types. | |
| CHECK(IsRealDataType(type)); | |
| if (!IsLocalSyncEnabled()) { | |
| // TODO(crbug.com/40260679): Verify whether it's actually necessary to check | |
| // IsActiveAccountInfoFullyLoaded() - can the engine actually start, and | |
| // data types become active, if that isn't true? | |
| if (!auth_manager_->IsActiveAccountInfoFullyLoaded()) { | |
| DVLOG(1) << "Waiting for refresh tokens to be loaded from the disk"; | |
| // GetDisableReasons() won't be empty until then. | |
| return DataTypeDownloadStatus::kWaitingForUpdates; | |
| } | |
| if (auth_manager_->IsSyncPaused()) { | |
| DVLOG(1) << "Error download status because sync is paused"; | |
| return DataTypeDownloadStatus::kError; | |
| } | |
| } | |
| // TODO(crbug.com/40260679): check whether this works when local sync is | |
| // enabled. | |
| if (!GetDisableReasons().empty() || !GetPreferredDataTypes().Has(type)) { | |
| DVLOG(1) | |
| << "Sync or " << DataTypeToDebugString(type) | |
| << " is disabled hence updates won't be downloaded from the server"; | |
| return DataTypeDownloadStatus::kError; | |
| } | |
| if (!engine_ || !engine_->IsInitialized()) { | |
| DVLOG(1) << "Waiting for the sync engine to be fully initialized"; | |
| return DataTypeDownloadStatus::kWaitingForUpdates; | |
| } | |
| if (data_type_manager_->GetDataTypesWithPermanentErrors().Has(type)) { | |
| DVLOG(1) << "Permanent error for " << DataTypeToDebugString(type); | |
| return DataTypeDownloadStatus::kError; | |
| } | |
| if (!GetActiveDataTypes().Has(type)) { | |
| DVLOG(1) << "Data type is not active yet"; | |
| return DataTypeDownloadStatus::kWaitingForUpdates; | |
| } | |
| if (!engine_->GetDetailedStatus().notifications_enabled) { | |
| DVLOG(1) << "Waiting for invalidations to be initialized"; | |
| return DataTypeDownloadStatus::kWaitingForUpdates; | |
| } | |
| // If there are any incoming invalidations or poll time elapsed, there can be | |
| // new updates to download from the server. | |
| if (engine_->GetDetailedStatus().invalidated_data_types.Has(type)) { | |
| DVLOG(1) << "There are incoming invalidations for: " | |
| << DataTypeToDebugString(type); | |
| return DataTypeDownloadStatus::kWaitingForUpdates; | |
| } | |
| // Wait for the poll request only during browser startup (i.e. when there were | |
| // not completed sync cycles). IsNextPollTimeInThePast() uses base::Time which | |
| // while poll scheduler uses base::TimeTicks. They may diverge in sleep mode | |
| // (TimeTicks may be paused) and it's possible that the actual timer for | |
| // polling will take longer. This might result in a long-standing | |
| // `kWaitingForUpdates` status. | |
| if (!HasCompletedSyncCycle() && engine_->IsNextPollTimeInThePast()) { | |
| DVLOG(1) << "Waiting for updates due an upcoming poll request"; | |
| return DataTypeDownloadStatus::kWaitingForUpdates; | |
| } | |
| return DataTypeDownloadStatus::kUpToDate; | |
| } | |
| void SyncServiceImpl::CacheTrustedVaultDebugInfoToPrefsFromEngine() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK(engine_); | |
| CHECK(engine_->IsInitialized()); | |
| sync_prefs_.SetCachedTrustedVaultAutoUpgradeExperimentGroup( | |
| engine_->GetDetailedStatus() | |
| .trusted_vault_debug_info.auto_upgrade_experiment_group()); | |
| RegisterTrustedVaultSyntheticFieldTrialsIfNecessary(); | |
| } | |
| CoreAccountInfo SyncServiceImpl::GetAccountInfo() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (!auth_manager_) { | |
| // Some crashes on iOS (crbug.com/962384) suggest that SyncServiceImpl | |
| // gets called after it has been already shutdown. It's not clear why this | |
| // actually happens. We add this null check here to protect against such | |
| // crashes. | |
| return CoreAccountInfo(); | |
| } | |
| return auth_manager_->GetActiveAccountInfo().account_info; | |
| } | |
| bool SyncServiceImpl::HasSyncConsent() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (!auth_manager_) { | |
| // This is a precautionary check to be consistent with the check in | |
| // GetAccountInfo(). | |
| return false; | |
| } | |
| return auth_manager_->GetActiveAccountInfo().is_sync_consented; | |
| } | |
| #if BUILDFLAG(IS_ANDROID) | |
| void SyncServiceImpl::SetInvalidationsForSessionsEnabled(bool enabled) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| sessions_invalidations_enabled_ = enabled; | |
| if (base::FeatureList::IsEnabled( | |
| kAlwaysRegisterSessionsInvalidationsAndroid)) { | |
| return; | |
| } | |
| UpdateDataTypesForInvalidations(); | |
| } | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| void SyncServiceImpl::SetPasskeysLocked(bool passkeys_locked) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (passkeys_locked_ == passkeys_locked) { | |
| return; | |
| } | |
| passkeys_locked_ = passkeys_locked; | |
| DVLOG(2) << "Notify observers on SetPasskeysLocked"; | |
| NotifyObservers(); | |
| } | |
| void SyncServiceImpl::SendExplicitPassphraseToPlatformClient() { | |
| RunOrQueueTaskOnEngineInitialized(base::BindOnce( | |
| &SyncServiceImpl::SendExplicitPassphraseToPlatformClientImpl, | |
| weak_factory_.GetWeakPtr())); | |
| } | |
| void SyncServiceImpl::SendExplicitPassphraseToPlatformClientImpl() { | |
| #if BUILDFLAG(IS_ANDROID) | |
| CHECK(engine_ && engine_->IsInitialized()); | |
| int version_code = 0; | |
| bool has_min_gms_version = | |
| base::StringToInt(base::android::device_info::gms_version_code(), | |
| &version_code) && | |
| version_code >= kMinGmsVersionCodeWithCustomPassphraseApi; | |
| has_min_gms_version |= base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| kIgnoreMinGmsVersionWithPassphraseSupportForTest); | |
| if (!has_min_gms_version) { | |
| return; | |
| } | |
| CHECK(crypto_.GetEncryptor()); | |
| CustomPassphraseBootstrapToken token = | |
| user_settings_->GetEncryptionBootstrapToken(*crypto_.GetEncryptor()); | |
| if (token.IsEmpty()) { | |
| return; | |
| } | |
| const sync_pb::NigoriKey& proto = token.ToProto(); | |
| int32_t byte_size = proto.ByteSizeLong(); | |
| std::vector<uint8_t> bytes(byte_size); | |
| proto.SerializeToArray(bytes.data(), byte_size); | |
| JNIEnv* env = base::android::AttachCurrentThread(); | |
| Java_ExplicitPassphrasePlatformClient_setExplicitDecryptionPassphrase( | |
| env, GetAccountInfo(), base::android::ToJavaByteArray(env, bytes)); | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| } | |
| void SyncServiceImpl::StopAndClear(ResetEngineReason reset_engine_reason) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| ClearUnrecoverableError(); | |
| ResetEngine(reset_engine_reason); | |
| // For explicit passphrase users, clear the encryption key, such that they | |
| // will need to reenter it if sync gets re-enabled. Note: the gaia-keyed | |
| // passphrase pref should be cleared before clearing | |
| // InitialSyncFeatureSetupComplete(). | |
| sync_prefs_.ClearAllEncryptionBootstrapTokens(); | |
| #if !BUILDFLAG(IS_CHROMEOS) | |
| // Note: ResetEngine() does *not* clear directly user-controlled prefs (such | |
| // as the set of selected types), so that if the user ever chooses to enable | |
| // Sync again, they start off with their previous settings by default. | |
| // However, they do have to go through the initial setup again. | |
| sync_prefs_.ClearInitialSyncFeatureSetupComplete(); | |
| #endif // !BUILDFLAG(IS_CHROMEOS) | |
| sync_prefs_.ClearPassphrasePromptMutedProductVersion(); | |
| // Cached information provided by SyncEngine must be cleared. | |
| sync_prefs_.ClearCachedPassphraseType(); | |
| sync_prefs_.ClearCachedTrustedVaultAutoUpgradeExperimentGroup(); | |
| // If the migration didn't finish before StopAndClear() was called, mark it as | |
| // done so it doesn't trigger again if the user signs in later. | |
| sync_prefs_.MarkPartialSyncToSigninMigrationFullyDone(); | |
| if (reset_engine_reason == ResetEngineReason::kNotSignedIn) { | |
| sync_prefs_.ClearCachedPersistentAuthErrorForMetrics(); | |
| } | |
| // Also let observers know that Sync-the-feature is now fully disabled | |
| // (before it possibly starts up again in transport-only mode). | |
| DVLOG(2) << "Notify observers on StopAndClear"; | |
| NotifyObservers(); | |
| } | |
| bool SyncServiceImpl::IsRetryingAccessTokenFetchForTest() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| return auth_manager_->IsRetryingAccessTokenFetchForTest(); // IN-TEST | |
| } | |
| std::string SyncServiceImpl::GetAccessTokenForTest() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| CHECK_IS_TEST(); | |
| return auth_manager_->GetCredentials().access_token_info.token; | |
| } | |
| SyncTokenStatus SyncServiceImpl::GetSyncTokenStatusForDebugging() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return auth_manager_->GetSyncTokenStatus(); | |
| } | |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | |
| void SyncServiceImpl::OverrideNetworkForTest( | |
| const CreateHttpPostProviderFactory& create_http_post_provider_factory_cb) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // If the engine has already been created, then it has a copy of the previous | |
| // HttpPostProviderFactory creation callback. In that case, shut down and | |
| // recreate the engine, so that it uses the correct (overridden) callback. | |
| // This is a horrible hack; the proper fix would be to inject the | |
| // callback in the ctor instead of adding it retroactively. | |
| // Note that ResetEngine() can't be used here, because it would caues the | |
| // engine to immediately restart. | |
| // TODO(crbug.com/41451146): Clean this up and inject required upon | |
| // construction. | |
| bool restart = false; | |
| if (engine_) { | |
| engine_->StopSyncingForShutdown(); | |
| data_type_manager_->Stop(SyncStopMetadataFate::KEEP_METADATA); | |
| data_type_manager_->SetConfigurer(nullptr); | |
| migrator_.reset(); | |
| crypto_.Reset(); | |
| engine_->Shutdown(ShutdownReason::STOP_SYNC_AND_KEEP_DATA); | |
| engine_.reset(); | |
| auth_manager_->ConnectionClosed(); | |
| restart = true; | |
| } | |
| DCHECK(!engine_); | |
| // If a previous request (with the wrong callback) already failed, the next | |
| // one would be backed off, which breaks tests. So reset the backoff. | |
| auth_manager_->ResetRequestAccessTokenBackoffForTest(); // IN-TEST | |
| // The null callback allows tests to easily reset to the default (real) | |
| // callback. | |
| create_http_post_provider_factory_override_for_test_ = | |
| create_http_post_provider_factory_cb | |
| ? std::make_optional(create_http_post_provider_factory_cb) | |
| : std::nullopt; | |
| if (restart) { | |
| TryStart(); | |
| } | |
| } | |
| #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | |
| SyncEncryptionHandler::Observer* | |
| SyncServiceImpl::GetEncryptionObserverForTest() { | |
| CHECK_IS_TEST(); | |
| return &crypto_; | |
| } | |
| void SyncServiceImpl::RemoveClientFromServer() const { | |
| if (!engine_ || !engine_->IsInitialized()) { | |
| return; | |
| } | |
| const std::string cache_guid = engine_->GetCacheGuid(); | |
| const std::string birthday = engine_->GetBirthday(); | |
| DCHECK(!cache_guid.empty()); | |
| const std::string& access_token = | |
| auth_manager_->GetCredentials().access_token_info.token; | |
| const bool report_sync_stopped = !access_token.empty() && !birthday.empty(); | |
| if (report_sync_stopped) { | |
| sync_stopped_reporter_->ReportSyncStopped(access_token, cache_guid, | |
| birthday); | |
| } | |
| } | |
| void SyncServiceImpl::RecordHistoryOptInStateOnSigninHistograms( | |
| signin_metrics::AccessPoint access_point, | |
| signin::ConsentLevel consent_level) { | |
| signin_metrics::RecordHistoryOptInStateOnSignin( | |
| access_point, consent_level, | |
| user_settings_->GetSelectedTypes().Has(UserSelectableType::kHistory)); | |
| } | |
| const GURL& SyncServiceImpl::GetSyncServiceUrlForDebugging() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return sync_service_url_; | |
| } | |
| base::FilePath SyncServiceImpl::GetLocalSyncBackendFolderForDebugging() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return sync_client_->GetLocalSyncBackendFolder(); | |
| } | |
| std::string SyncServiceImpl::GetUnrecoverableErrorMessageForDebugging() const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return unrecoverable_error_message_; | |
| } | |
| base::Location SyncServiceImpl::GetUnrecoverableErrorLocationForDebugging() | |
| const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| return unrecoverable_error_location_; | |
| } | |
| void SyncServiceImpl::OnSetupInProgressHandleDestroyed() { | |
| DCHECK_GT(outstanding_setup_in_progress_handles_, 0); | |
| --outstanding_setup_in_progress_handles_; | |
| // The user closed a setup UI, and will expect their changes to actually | |
| // take effect now. So we reconfigure here even if another setup UI happens | |
| // to be open right now. | |
| ConfigureDataTypeManager(ConfigureReason::kReconfiguration, | |
| /*bypass_setup_in_progress_check=*/true); | |
| } | |
| void SyncServiceImpl::RunOrQueueTaskOnEngineInitialized( | |
| base::OnceClosure task) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| if (engine_ && engine_->IsInitialized()) { | |
| std::move(task).Run(); | |
| return; | |
| } | |
| tasks_waiting_for_engine_initialization_.push_back(std::move(task)); | |
| } | |
| void SyncServiceImpl::GetTypesWithUnsyncedData( | |
| DataTypeSet requested_types, | |
| base::OnceCallback<void(absl::flat_hash_map<DataType, size_t>)> callback) | |
| const { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Syncing users do not use separate local and account storages. Thus, there's | |
| // no local-only data. The same is true for local sync users. | |
| if (HasSyncConsent() || IsLocalSyncEnabled()) { | |
| std::move(callback).Run({}); | |
| return; | |
| } | |
| data_type_manager_->GetTypesWithUnsyncedData(requested_types, | |
| std::move(callback)); | |
| } | |
| void SyncServiceImpl::GetLocalDataDescriptions( | |
| DataTypeSet types, | |
| base::OnceCallback<void(std::map<DataType, LocalDataDescription>)> | |
| callback) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| // Some code paths in GetLocalDataDescriptionsImpl() are synchronous, e.g. | |
| // if `types` have synchronous DataTypeLocalDataBatchUploader implementations. | |
| // Having an API that is sometime sync and sometimes async can be unexpected | |
| // to the caller and lead to bugs such as crbug.com/361088051. To avoid those, | |
| // post a task here to ensure the call is always async. | |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(&SyncServiceImpl::GetLocalDataDescriptionsImpl, | |
| weak_factory_.GetWeakPtr(), types, std::move(callback))); | |
| } | |
| void SyncServiceImpl::GetLocalDataDescriptionsImpl( | |
| DataTypeSet types, | |
| base::OnceCallback<void(std::map<DataType, LocalDataDescription>)> | |
| callback) { | |
| // Syncing users do not use separate local and account storages. Thus, there's | |
| // no local-only data. The same is true for local sync users. | |
| if (HasSyncConsent() || IsLocalSyncEnabled()) { | |
| std::move(callback).Run({}); | |
| return; | |
| } | |
| data_type_manager_->GetLocalDataDescriptions(types, std::move(callback)); | |
| } | |
| void SyncServiceImpl::TriggerLocalDataMigration(DataTypeSet types) { | |
| for (DataType type : types) { | |
| base::UmaHistogramEnumeration("Sync.BatchUpload.Requests3", | |
| syncer::DataTypeHistogramValue(type)); | |
| } | |
| // Syncing users do not use separate local and account storages. Thus, there's | |
| // no local-only data to migrate. The same is true for local sync users. | |
| if (HasSyncConsent() || IsLocalSyncEnabled()) { | |
| return; | |
| } | |
| return data_type_manager_->TriggerLocalDataMigration(types); | |
| } | |
| void SyncServiceImpl::TriggerLocalDataMigrationForItems( | |
| std::map<DataType, std::vector<LocalDataItemModel::DataId>> items) { | |
| for (const auto& [type, _] : items) { | |
| base::UmaHistogramEnumeration("Sync.BatchUpload.Requests3", | |
| syncer::DataTypeHistogramValue(type)); | |
| } | |
| // Syncing users do not use separate local and account storages. Thus, there's | |
| // no local-only data to migrate. The same is true for local sync users. | |
| if (HasSyncConsent() || IsLocalSyncEnabled()) { | |
| return; | |
| } | |
| return data_type_manager_->TriggerLocalDataMigrationForItems( | |
| std::move(items)); | |
| } | |
| void SyncServiceImpl::SelectTypeAndMigrateLocalDataItemsWhenActive( | |
| DataType data_type, | |
| std::vector<LocalDataItemModel::DataId> items) { | |
| if (IsLocalSyncEnabled()) { | |
| // This function should not have been called for local sync (i.e. enterprise | |
| // roaming profiles). However, as additional safeguard, the call is ignored | |
| // to avoid any unexpected side-effects. | |
| return; | |
| } | |
| CHECK(local_data_migration_item_queue_); | |
| CHECK(IsSignedIn()); | |
| // Syncing users do not use separate local and account storages. Thus, there's | |
| // no local-only data to migrate. The sign in through the promo should not | |
| // have made the user consent to sync. | |
| CHECK(!HasSyncConsent()); | |
| std::optional<UserSelectableType> user_selectable_type = | |
| GetUserSelectableTypeFromDataType(data_type); | |
| CHECK(user_selectable_type.has_value()); | |
| // Do not proceed if the data type is managed for the account, disabled by | |
| // policy or not available in transport-only mode. | |
| if (GetUserSettings()->IsTypeManagedByPolicy(user_selectable_type.value()) || | |
| HasDisableReason(SyncService::DISABLE_REASON_ENTERPRISE_POLICY) || | |
| !data_type_manager_->GetDataTypesForTransportOnlyMode().Has(data_type)) { | |
| return; | |
| } | |
| // Unset the user's preference for account storage if they had explicitly been | |
| // using local storage before. This will enable account storage for the type | |
| // by its default value set in the sync prefs. | |
| if (!GetUserSettings()->GetSelectedTypes().Has( | |
| user_selectable_type.value())) { | |
| GetUserSettings()->ResetSelectedType(user_selectable_type.value()); | |
| } | |
| // At this point, the type should be selected. | |
| CHECK( | |
| GetUserSettings()->GetSelectedTypes().Has(user_selectable_type.value())); | |
| // Move the item as soon as the sync service activates. | |
| local_data_migration_item_queue_ | |
| ->TriggerLocalDataMigrationForItemsWhenTypeBecomesActive( | |
| data_type, std::move(items)); | |
| } | |
| void SyncServiceImpl::AcknowledgeBookmarksLimitExceededError( | |
| BookmarksLimitExceededHelpClickedSource source) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| base::UmaHistogramEnumeration("Sync.BookmarksLimitExceededHelpClickedSource", | |
| source); | |
| bookmark_sync_error_state_.AcknowledgeError(); | |
| } | |
| } // namespace syncer | |
| #if BUILDFLAG(IS_ANDROID) | |
| DEFINE_JNI(ExplicitPassphrasePlatformClient) | |
| #endif |