| // 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/barrier_callback.h" |
| #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/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/configure_context.h" |
| #include "components/sync/service/data_type_manager_impl.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 "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/engine/nigori/nigori.h" |
| #include "components/sync/protocol/nigori_specifics.pb.h" |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| namespace syncer { |
| |
| namespace { |
| |
| BASE_FEATURE(kSyncUnsubscribeFromTypesWithPermanentErrors, |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| |
| #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)), |
| 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_EQ(base::FeatureList::IsEnabled(syncer::kSyncUseOsCryptAsync), |
| os_crypt_async_ != nullptr); |
| |
| // 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); |
| sync_prefs_.MaybeMigrateCustomPassphrasePref(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) |
| // 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(); |
| #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()); |
| } |
| |
| 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() { |
| if (base::FeatureList::IsEnabled(syncer::kSyncUseOsCryptAsync)) { |
| 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. Two |
| // instances of Encryptor are needed, one for SyncServiceImpl and one for |
| // SyncEngine. |
| auto on_encryptors_gotten = |
| base::BindOnce(&SyncServiceImpl::TryStartImpl, |
| weak_factory_.GetWeakPtr(), base::TimeTicks::Now()); |
| |
| auto barrier = base::BarrierCallback<os_crypt_async::Encryptor>( |
| 2, std::move(on_encryptors_gotten)); |
| |
| // TODO(419157433): Remove the option to get the encryptor for SyncEngine |
| // once the kSyncUseOsCryptAsync feature is enabled by default. |
| os_crypt_async_->GetInstance( |
| barrier, os_crypt_async::Encryptor::Option::kEncryptSyncCompat); |
| os_crypt_async_->GetInstance( |
| barrier, os_crypt_async::Encryptor::Option::kEncryptSyncCompat); |
| } else { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&SyncServiceImpl::TryStartImpl, |
| weak_factory_.GetWeakPtr(), base::TimeTicks::Now(), |
| std::vector<os_crypt_async::Encryptor>())); |
| } |
| } |
| |
| void SyncServiceImpl::TryStartImpl( |
| base::TimeTicks try_start_time, |
| std::vector<os_crypt_async::Encryptor> encryptors) { |
| base::Time deferral_time; |
| std::swap(deferring_first_start_since_, deferral_time); |
| |
| if (engine_ || !IsEngineAllowedToRun()) { |
| return; |
| } |
| |
| std::unique_ptr<os_crypt_async::Encryptor> engine_encryptor; |
| if (!encryptors.empty()) { |
| CHECK_EQ(encryptors.size(), 2u); |
| base::UmaHistogramTimes("Sync.EncryptorReceivedTime", |
| base::TimeTicks::Now() - try_start_time); |
| crypto_.SetEncryptor(std::make_unique<os_crypt_async::Encryptor>( |
| std::move(encryptors.at(0)))); |
| engine_encryptor = std::make_unique<os_crypt_async::Encryptor>( |
| std::move(encryptors.at(1))); |
| } else { |
| crypto_.SetEncryptor(nullptr); |
| } |
| |
| 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.service_url = sync_service_url_; |
| params.http_factory_getter = base::BindOnce( |
| create_http_post_provider_factory_override_for_test_.value_or( |
| create_http_post_provider_factory_), |
| MakeUserAgentForSync(channel_), url_loader_factory_->Clone()); |
| params.authenticated_account_info = authenticated_account_info; |
| |
| params.sync_manager_factory = |
| std::make_unique<SyncManagerFactory>(network_connection_tracker_); |
| if (sync_prefs_.IsLocalSyncEnabled()) { |
| params.enable_local_sync_backend = true; |
| params.local_sync_backend_folder = |
| sync_client_->GetLocalSyncBackendFolder(); |
| } |
| params.engine_components_factory = |
| std::make_unique<EngineComponentsFactoryImpl>( |
| EngineSwitchesFromCommandLine()); |
| |
| params.encryptor = std::move(engine_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"); |
| |
| NotifyShutdown(); |
| |
| // 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; |
| } |
| |
| if (base::FeatureList::IsEnabled(kSyncDetermineAccountManagedStatus)) { |
| // 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) |
| 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) |
| |
| if (GetAuthError().state() != GoogleServiceAuthError::NONE) { |
| 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 (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) |
| |
| return UserActionableError::kNone; |
| } |
| |
| void SyncServiceImpl::NotifyObservers() { |
| 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); |
| } |
| } |
| |
| void SyncServiceImpl::NotifySyncCycleCompleted() { |
| CHECK(observers_); |
| for (SyncServiceObserver& observer : *observers_) { |
| observer.OnSyncCycleCompleted(this); |
| } |
| } |
| |
| 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(CONFIGURE_REASON_EXISTING_CLIENT_RESTART, |
| /*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 |
| // 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::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_) { |
| TryStart(); |
| NotifyObservers(); |
| } else { |
| ConfigureDataTypeManager(CONFIGURE_REASON_RECONFIGURATION, |
| /*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.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 the engine already exists, just propagate the new credentials. |
| SyncCredentials credentials = auth_manager_->GetCredentials(); |
| if (credentials.access_token.empty()) { |
| engine_->InvalidateCredentials(); |
| } else { |
| engine_->UpdateCredentials(credentials); |
| } |
| } |
| |
| 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(CONFIGURE_REASON_CRYPTO, |
| /*bypass_setup_in_progress_check=*/false); |
| } |
| |
| void SyncServiceImpl::PassphraseTypeChanged(PassphraseType passphrase_type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| #if !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)) |
| // 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) 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) && |
| base::FeatureList::IsEnabled(kReplaceSyncPromosWithSignInPromos)) { |
| 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 std::string& bootstrap_token) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| user_settings_->SetEncryptionBootstrapToken(bootstrap_token); |
| SendExplicitPassphraseToPlatformClient(); |
| } |
| |
| std::string SyncServiceImpl::GetEncryptionBootstrapToken() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return user_settings_->GetEncryptionBootstrapToken(); |
| } |
| |
| 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(CONFIGURE_REASON_RECONFIGURATION, |
| /*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(CONFIGURE_REASON_RECONFIGURATION, |
| /*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 (base::FeatureList::IsEnabled(kSyncDetermineAccountManagedStatus)) { |
| // 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(); |
| |
| base::UmaHistogramBoolean("Sync.ConfigureDataTypeManager.IsGaiaAccountId", |
| GetAccountInfo().account_id.ToString() == |
| GetAccountInfo().gaia.ToString()); |
| |
| 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), CONFIGURE_REASON_MIGRATION, |
| /*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 = CONFIGURE_REASON_NEW_CLIENT; |
| } |
| } |
| |
| DCHECK(!configure_context.authenticated_gaia_id.empty() || |
| IsLocalSyncEnabled()); |
| DCHECK(!configure_context.cache_guid.empty()); |
| DCHECK_NE(configure_context.reason, CONFIGURE_REASON_UNKNOWN); |
| |
| 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::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 (!sessions_invalidations_enabled_) { |
| types.Remove(SESSIONS); |
| } |
| |
| if (!data_type_manager_->GetDataTypesWithPermanentErrors().empty() && |
| base::FeatureList::IsEnabled( |
| kSyncUnsubscribeFromTypesWithPermanentErrors)) { |
| // Unsubscribe from data types with permanent errors. Types which are |
| // unready or have crypto errors are intentionally kept because they will |
| // 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 |
| } |
| |
| BackendMigrator* SyncServiceImpl::GetBackendMigratorForTest() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK_IS_TEST(); |
| return migrator_.get(); |
| } |
| |
| 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) |
| // 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(CONFIGURE_REASON_RECONFIGURATION, |
| /*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(base::NotFatalUntil::M142); |
| } |
| |
| 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::Value::List)> 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; |
| } |
| |
| void SyncServiceImpl::SetInvalidationsForSessionsEnabled(bool enabled) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| sessions_invalidations_enabled_ = enabled; |
| UpdateDataTypesForInvalidations(); |
| } |
| |
| 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; |
| } |
| |
| std::unique_ptr<syncer::Nigori> nigori_key = |
| crypto_.GetExplicitPassphraseDecryptionNigoriKey(); |
| if (!nigori_key) { |
| return; |
| } |
| |
| sync_pb::NigoriKey proto; |
| proto.set_deprecated_name(nigori_key->GetKeyName()); |
| nigori_key->ExportKeys(proto.mutable_deprecated_user_key(), |
| proto.mutable_encryption_key(), |
| proto.mutable_mac_key()); |
| 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_->access_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_->access_token(); |
| const bool report_sync_stopped = !access_token.empty() && !birthday.empty(); |
| base::UmaHistogramBoolean("Sync.SyncStoppedReported", report_sync_stopped); |
| 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_; |
| } |
| |
| 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(CONFIGURE_REASON_RECONFIGURATION, |
| /*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()); |
| |
| // TODO(crbug.com/386752831): Add a metric here. |
| |
| 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)); |
| } |
| |
| } // namespace syncer |