// 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/custom_passphrase_bootstrap_token.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/data_type_histogram.h"
#include "components/sync/base/features.h"
#include "components/sync/base/stop_source.h"
#include "components/sync/base/sync_util.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/engine/configure_reason.h"
#include "components/sync/engine/engine_components_factory_impl.h"
#include "components/sync/engine/net/http_post_provider_factory.h"
#include "components/sync/engine/shutdown_reason.h"
#include "components/sync/engine/sync_encryption_handler.h"
#include "components/sync/invalidations/sync_invalidations_service.h"
#include "components/sync/service/backend_migrator.h"
#include "components/sync/service/bookmark_sync_error_state.h"
#include "components/sync/service/configure_context.h"
#include "components/sync/service/data_type_manager_impl.h"
#include "components/sync/service/data_type_status_table.h"
#include "components/sync/service/device_statistics_request_impl.h"
#include "components/sync/service/glue/sync_transport_data_prefs.h"
#include "components/sync/service/local_data_description.h"
#include "components/sync/service/local_data_migration_item_queue.h"
#include "components/sync/service/sync_auth_manager.h"
#include "components/sync/service/sync_engine_factory.h"
#include "components/sync/service/sync_error.h"
#include "components/sync/service/sync_feature_status_for_migrations_recorder.h"
#include "components/sync/service/sync_prefs.h"
#include "components/sync/service/sync_prefs_policy_handler.h"
#include "components/sync/service/sync_service_utils.h"
#include "components/sync/service/trusted_vault_histograms.h"
#include "components/sync/service/trusted_vault_synthetic_field_trial.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/device_info.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/memory/scoped_refptr.h"
#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
#include "components/sync/android/jni_headers/ExplicitPassphrasePlatformClient_jni.h"
#include "components/sync/android/sync_service_android_bridge.h"
#include "components/sync/model/crypto/nigori.h"
#include "components/sync/protocol/nigori_specifics.pb.h"
#endif  // BUILDFLAG(IS_ANDROID)

namespace syncer {

namespace {

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,
          init_params.account_managed_status_finder_timeout)),
      channel_(init_params.channel),
      debug_identifier_(std::move(init_params.debug_identifier)),
      sync_service_url_(
          GetSyncServiceURL(*base::CommandLine::ForCurrentProcess(), channel_)),
      crypto_(this, sync_client_->GetTrustedVaultClient()),
      url_loader_factory_(std::move(init_params.url_loader_factory)),
      network_connection_tracker_(
          std::move(init_params.network_connection_tracker)) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sync_client_);
  DCHECK(IsLocalSyncEnabled() || identity_manager_ != nullptr);
  CHECK(os_crypt_async_);

  // If Sync is disabled via command line flag, then SyncServiceImpl
  // shouldn't be instantiated.
  DCHECK(IsSyncAllowedByFlag());

  sync_stopped_reporter_ = std::make_unique<SyncStoppedReporter>(
      sync_service_url_, MakeUserAgentForSync(channel_), url_loader_factory_);

  if (identity_manager_) {
    identity_manager_observation_.Observe(identity_manager_);
  }

  observers_.emplace();

  // Based on the information cached in preferences, it might be required to
  // register a synthetic field trial group. This should be done as early as
  // possible to avoid untagged metrics if they get logged before other events
  // like sync engine initialization, which could take arbitrarily long (e.g.
  // persistent auth error). Task-posting is involved to avoid infinite
  // recursions if the implementation in SyncClient leads to
  // accessing/constructing SyncService.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &SyncServiceImpl::RegisterTrustedVaultSyntheticFieldTrialsIfNecessary,
          weak_factory_.GetWeakPtr()));
}

void SyncServiceImpl::RegisterTrustedVaultSyntheticFieldTrialsIfNecessary() {
  if (trusted_vault_auto_upgrade_synthetic_field_trial_registered_) {
    // Registration function already invoked. It cannot be invoked twice, as
    // runtime changes to the group assignment is not supported (e.g. signout).
    return;
  }

  const sync_pb::TrustedVaultAutoUpgradeExperimentGroup proto =
      sync_prefs_.GetCachedTrustedVaultAutoUpgradeExperimentGroup().value_or(
          sync_pb::TrustedVaultAutoUpgradeExperimentGroup());

  const TrustedVaultAutoUpgradeSyntheticFieldTrialGroup group =
      TrustedVaultAutoUpgradeSyntheticFieldTrialGroup::FromProto(proto);

  if (!group.is_valid()) {
    // Broadcasting an invalid group isn't allowed, as it would otherwise use
    // the only chance to invoke the registration function below, which may only
    // be invoked once.
    return;
  }

  trusted_vault_auto_upgrade_synthetic_field_trial_registered_ = true;
  sync_client_->RegisterTrustedVaultAutoUpgradeSyntheticFieldTrial(group);
}

SyncServiceImpl::~SyncServiceImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Shutdown() should have been called before destruction.
  DCHECK(!engine_);
}

void SyncServiceImpl::Initialize(DataTypeController::TypeVector controllers) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  data_type_manager_ = std::make_unique<DataTypeManagerImpl>(
      std::move(controllers), &crypto_, this);

  // It's safe to pass a raw ptr, since SyncServiceImpl outlives
  // SyncUserSettingsImpl.
  user_settings_ = std::make_unique<SyncUserSettingsImpl>(
      /*delegate=*/this, &crypto_, &sync_prefs_,
      data_type_manager_->GetRegisteredDataTypes());

  if (!IsLocalSyncEnabled()) {
    auth_manager_->RegisterForAuthNotifications();

    // Trigger a refresh when additional data types get enabled for
    // invalidations. This is needed to get the latest data after subscribing
    // for the updates.
    sync_client_->GetSyncInvalidationsService()
        ->SetCommittedAdditionalInterestedDataTypesCallback(base::BindRepeating(
            &SyncServiceImpl::TriggerRefresh, weak_factory_.GetWeakPtr(),
            TriggerRefreshSource::kSyncInvalidationsService));

    // TODO(crbug.com/40257467): revisit this logic. IsSignedIn() doesn't feel
    // the right condition to check.
    if (IsSignedIn()) {
      // Start receiving invalidations as soon as possible since GCMDriver drops
      // incoming FCM messages otherwise. The messages will be collected by
      // SyncInvalidationsService until sync engine is initialized and ready to
      // handle invalidations.
      sync_client_->GetSyncInvalidationsService()->StartListening();
    }
  }

  // *After* setting up `auth_manager_`, run pref migrations that depend on
  // the account state.
  sync_prefs_.MaybeMigratePrefsForSyncToSigninPart1(
      GetSyncAccountStateForPrefs(), GetAccountInfo().gaia);
  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)
    // Disable OS data types to avoid automatic local data upload upon policy
    // removal, as OS data types do not support dual storage with UNO.
    if (!HasSyncConsent() && IsReplaceSyncPromosWithSignInPromosEnabled()) {
      user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/false,
                                         UserSelectableOsTypeSet());
    } else {
      // On ChromeOS Ash, sync-the-feature stays disabled even after the policy
      // is removed, for historic reasons. It is unclear if this behavior is
      // optional, because it is indistinguishable from the
      // sync-reset-via-dashboard case. It can be resolved by invoking
      // ClearSyncFeatureDisabledViaDashboard().
      user_settings_->SetSyncFeatureDisabledViaDashboard();
    }
#endif  // BUILDFLAG(IS_CHROMEOS)
  } else if (HasDisableReason(DISABLE_REASON_NOT_SIGNED_IN)) {
    // On ChromeOS-Ash, signout is not possible, so it's not necessary to handle
    // this case.
    // TODO(crbug.com/40272157): It *should* be harmless to handle this case on
    // ChromeOS-Ash since it's supposedly unreachable, *but* during the very
    // first startup of a fresh profile, the signed-in account isn't known yet
    // at this point (see also https://crbug.com/1458701#c7).
#if !BUILDFLAG(IS_CHROMEOS)
    StopAndClear(ResetEngineReason::kNotSignedIn);
#endif
  }

  const bool is_sync_feature_requested_for_metrics =
      IsLocalSyncEnabled() ||
#if BUILDFLAG(IS_CHROMEOS)
      (!user_settings_->IsSyncFeatureDisabledViaDashboard() &&
       (!base::FeatureList::IsEnabled(kReplaceSyncPromosWithSignInPromos) ||
        HasSyncConsent()));
#else
      HasSyncConsent();
#endif  // BUILDFLAG(IS_CHROMEOS)

  // Note: We need to record the initial state *after* calling
  // RegisterForAuthNotifications(), because before that the authenticated
  // account isn't initialized.
  RecordSyncInitialState(GetDisableReasons(),
                         is_sync_feature_requested_for_metrics,
                         user_settings_->IsInitialSyncFeatureSetupComplete());

  // Call Stop() on controllers for non-preferred types to clear metadata.
  // This allows clearing metadata for types disabled in previous run early-on
  // during initialization.
  data_type_manager_->ClearMetadataWhileStoppedExceptFor(
      GetPreferredDataTypes());

  if (IsEngineAllowedToRun()) {
    if (!sync_client_->GetSyncEngineFactory()
             ->HasTransportDataIncludingFirstSync(
                 signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia))) {
      // Sync never initialized before on this profile, so let's try immediately
      // the very first time. This is particularly useful for Chrome Ash (where
      // the user is signed in to begin with) and local sync (where sign-in
      // state doesn't matter to start the engine).
      TryStart();
    } else {
      // Defer starting the engine, for browser startup performance. If another
      // TryStart() happens in the meantime, this deferred task will no-op.
      deferring_first_start_since_ = base::Time::Now();
      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&SyncServiceImpl::TryStart,
                         weak_factory_.GetWeakPtr()),
          GetDeferredInitDelay());
    }
  }

  sync_status_recorder_ =
      std::make_unique<SyncFeatureStatusForMigrationsRecorder>(
          sync_client_->GetPrefService(), this);

  local_data_migration_item_queue_ =
      std::make_unique<LocalDataMigrationItemQueue>(this,
                                                    data_type_manager_.get());

  device_statistics_scheduler_ = std::make_unique<DeviceStatisticsScheduler>(
      /*delegate=*/this, sync_client_->GetPrefService(),
      sync_client_->GetIdentityManager(), sync_service_url_);
}

void SyncServiceImpl::StartSyncingWithServer() {
  if (engine_) {
    engine_->StartSyncingWithServer();
  }
  if (IsLocalSyncEnabled()) {
    TriggerRefresh(TriggerRefreshSource::kLocalSync, DataTypeSet::All());
  }
}

DataTypeSet SyncServiceImpl::GetRegisteredDataTypesForTest() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();
  return data_type_manager_->GetRegisteredDataTypes();
}

bool SyncServiceImpl::HasAnyModelErrorForTest(DataTypeSet types) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();
  CHECK(data_type_manager_);

  for (DataType type : types) {
    DataTypeController* controller =
        data_type_manager_->GetControllerForTest(type);  // IN-TEST
    if (controller && controller->state() == DataTypeController::FAILED) {
      return true;
    }
  }
  return false;
}

void SyncServiceImpl::GetThrottledDataTypesForTest(
    base::OnceCallback<void(DataTypeSet)> cb) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();

  if (!engine_ || !engine_->IsInitialized()) {
    std::move(cb).Run(DataTypeSet());
    return;
  }

  engine_->GetThrottledDataTypesForTest(std::move(cb));  // IN-TEST
}

size_t SyncServiceImpl::GetQueuedLocalDataMigrationItemCountForTest() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();

  return local_data_migration_item_queue_
      ->GetItemsCountForTesting();  // IN-TEST
}

// static
ShutdownReason SyncServiceImpl::ShutdownReasonForResetEngineReason(
    ResetEngineReason reset_reason) {
  switch (reset_reason) {
    case ResetEngineReason::kShutdown:
      return ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA;
    case ResetEngineReason::kCredentialsChanged:
      return ShutdownReason::STOP_SYNC_AND_KEEP_DATA;
    case ResetEngineReason::kUnrecoverableError:
    case ResetEngineReason::kDisabledAccount:
    case ResetEngineReason::kResetLocalData:
    case ResetEngineReason::kUpgradeClientError:
    case ResetEngineReason::kNotSignedIn:
    case ResetEngineReason::kEnterprisePolicy:
    case ResetEngineReason::kDisableSyncOnClient:
      return ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA;
  }
}

bool SyncServiceImpl::ShouldClearTransportDataForAccount(
    ResetEngineReason reset_reason) {
  switch (reset_reason) {
    case ResetEngineReason::kShutdown:
    case ResetEngineReason::kDisabledAccount:
    case ResetEngineReason::kUpgradeClientError:
    case ResetEngineReason::kCredentialsChanged:
    case ResetEngineReason::kNotSignedIn:
    case ResetEngineReason::kEnterprisePolicy:
      // Regular/benign cases; no need to clear.
      return false;
    case ResetEngineReason::kUnrecoverableError:
    case ResetEngineReason::kResetLocalData:
    case ResetEngineReason::kDisableSyncOnClient:
      // Weird error, or explicit request to reset. Clear transport data to
      // start over fresh.
      return true;
  }
}

bool SyncServiceImpl::IsEngineAllowedToRun() const {
  return GetDisableReasons().empty() && !auth_manager_->IsSyncPaused();
}

void SyncServiceImpl::OnProtocolEvent(const ProtocolEvent& event) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  for (ProtocolEventObserver& observer : protocol_event_observers_) {
    observer.OnProtocolEvent(event);
  }
}

void SyncServiceImpl::OnDataTypeRequestsSyncStartup(DataType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(UserTypes().Has(type));

  if (!GetPreferredDataTypes().Has(type)) {
    // We can get here as datatype SyncableServices are typically wired up
    // to the native datatype even if sync isn't enabled.
    DVLOG(1) << "Dropping sync startup request because type "
             << DataTypeToDebugString(type) << "not enabled.";
    return;
  }

  if (engine_) {
    DVLOG(1) << "A data type requested sync startup, but it looks like "
                "something else beat it to the punch.";
    return;
  }

  TryStart();
}

void SyncServiceImpl::TryStart() {
  CHECK(os_crypt_async_);
  // It's possible for this to be called multiple times before the callback
  // runs (e.g. if the user signs out and back in again). This is safe, as
  // OSCryptAsync will just queue the callbacks and run them once the
  // encryptor is available. The first call to TryStartImpl() that succeeds
  // will create the engine, and subsequent ones will be no-ops.
  // TODO(crbug.com/514283732): Now that Encryptor is a refcounted object, we
  // can the barrier is probably not needed anymore. Investigate if it can be
  // removed.
  auto barrier =
      base::BarrierCallback<scoped_refptr<os_crypt_async::Encryptor>>(
          2,
          base::BindOnce(&SyncServiceImpl::TryStartImpl,
                         weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));

  // One instance of Encryptor is needed for SyncServiceImpl and one for
  // SyncEngine.
  os_crypt_async_->GetInstance(barrier);
  os_crypt_async_->GetInstance(barrier);
}

void SyncServiceImpl::TryStartImpl(
    base::TimeTicks try_start_time,
    std::vector<scoped_refptr<os_crypt_async::Encryptor>> encryptors) {
  base::Time deferral_time;
  std::swap(deferring_first_start_since_, deferral_time);

  if (engine_ || !IsEngineAllowedToRun()) {
    return;
  }

  CHECK_EQ(encryptors.size(), 2u);

  base::UmaHistogramTimes("Sync.EncryptorReceivedTime",
                          base::TimeTicks::Now() - try_start_time);

  // One instance of Encryptor is needed for SyncServiceImpl and one for
  // SyncEngine.
  crypto_.SetEncryptor(std::move(encryptors[0]));

  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(encryptors[1]);

  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();

  device_statistics_scheduler_.reset();

  // Ensure the LocalDataMigrationItemQueue, the DataTypeManager and the
  // engine are destroyed in order since they hold consecutive pointers to each
  // other.
  std::unique_ptr<SyncEngine> engine =
      ResetEngine(ResetEngineReason::kShutdown);
  local_data_migration_item_queue_.reset();
  data_type_manager_.reset();
  engine.reset();

  crypto_.StopObservingTrustedVaultClient();

  // All observers must be gone now: All KeyedServices should have unregistered
  // their observers already before, in their own Shutdown(), and all others
  // should have done it now when they got the shutdown notification.
  // (Note that destroying the ObserverList triggers its "check_empty" check.)
  observers_.reset();

  auth_manager_.reset();

  identity_manager_observation_.Reset();
}

std::unique_ptr<SyncEngine> SyncServiceImpl::ResetEngine(
    ResetEngineReason reset_reason) {
  TRACE_EVENT0("sync", "SyncServiceImpl::ResetEngine");
  CHECK(data_type_manager_);

  tasks_waiting_for_engine_initialization_.clear();

  const ShutdownReason shutdown_reason =
      ShutdownReasonForResetEngineReason(reset_reason);

  // Stop all data type controllers, if needed. Note that until Stop completes,
  // it is possible in theory to have a ChangeProcessor apply a change from a
  // native model. In that case, it will get applied to the local storage as an
  // unsynced change. That will be persisted, and committed on restart.
  if (shutdown_reason != ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA) {
    data_type_manager_->Stop(
        ShutdownReasonToSyncStopMetadataFate(shutdown_reason));
    data_type_manager_->SetConfigurer(nullptr);
  }

  if (!engine_) {
    // If the engine hasn't started or is already shut down when a DISABLE_SYNC
    // happens, the Directory needs to be cleaned up here.
    if (shutdown_reason == ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA) {
      sync_client_->GetSyncEngineFactory()->CleanupOnDisableSync();
    }
    // Depending on the `reset_reason`, maybe clear account-keyed transport
    // data.
    if (ShouldClearTransportDataForAccount(reset_reason)) {
      sync_client_->GetSyncEngineFactory()->ClearTransportDataForAccount(
          signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia));
    }
    return nullptr;
  }

  base::UmaHistogramEnumeration("Sync.ResetEngineReason", reset_reason);
  switch (shutdown_reason) {
    case ShutdownReason::STOP_SYNC_AND_KEEP_DATA:
      // Do not stop listening for sync invalidations. Otherwise, GCMDriver
      // would drop all the incoming messages.
      RemoveClientFromServer();
      break;
    case ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA: {
      sync_client_->GetSyncInvalidationsService()->StopListeningPermanently();
      RemoveClientFromServer();
      break;
    }
    case ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA:
      sync_client_->GetSyncInvalidationsService()->StopListening();
      break;
  }

  // First, we spin down the engine to stop change processing as soon as
  // possible.
  engine_->StopSyncingForShutdown();

  // Shutdown the migrator before the engine to ensure it doesn't pull a null
  // snapshot.
  migrator_.reset();

  engine_->Shutdown(shutdown_reason);
  std::unique_ptr<SyncEngine> engine_to_be_destroyed = std::move(engine_);

  // Clear various state.
  crypto_.Reset();
  last_snapshot_ = SyncCycleSnapshot();

  // Depending on the `reset_reason`, maybe clear account-keyed transport data.
  if (ShouldClearTransportDataForAccount(reset_reason)) {
    sync_client_->GetSyncEngineFactory()->ClearTransportDataForAccount(
        signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia));
  }

  if (!IsLocalSyncEnabled()) {
    auth_manager_->ConnectionClosed();
  }

  DVLOG(2) << "Notify observers on reset engine";
  NotifyObservers();

  // Now that everything is shut down, try to start up again.
  switch (shutdown_reason) {
    case ShutdownReason::STOP_SYNC_AND_KEEP_DATA:
    case ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA:
      // If Sync is being stopped (either temporarily or permanently),
      // immediately try to start up again. Note that this might start only the
      // transport mode, or it might not start anything at all if something is
      // preventing Sync startup (e.g. the user signed out).
      // Note that TryStart() is guaranteed to *not* have a synchronous effect
      // (it posts a task).
      TryStart();
      break;
    case ShutdownReason::BROWSER_SHUTDOWN_AND_KEEP_DATA:
      // The only exception is browser shutdown: In this case, there's clearly
      // no point in starting up again.
      break;
  }

  return engine_to_be_destroyed;
}

#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject> SyncServiceImpl::GetJavaObject() {
  if (!sync_service_android_) {
    sync_service_android_ = std::make_unique<SyncServiceAndroidBridge>(this);
  }
  return sync_service_android_->GetJavaObject();
}
#endif  // BUILDFLAG(IS_ANDROID)

SyncUserSettings* SyncServiceImpl::GetUserSettings() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return user_settings_.get();
}

const SyncUserSettings* SyncServiceImpl::GetUserSettings() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return user_settings_.get();
}

SyncService::DisableReasonSet SyncServiceImpl::GetDisableReasons() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // If Sync is disabled via command line flag, then SyncServiceImpl
  // shouldn't even be instantiated.
  DCHECK(IsSyncAllowedByFlag());
  DisableReasonSet result;

  // If local sync is enabled, most disable reasons don't apply.
  if (!IsLocalSyncEnabled()) {
    if (user_settings_->IsSyncClientDisabledByPolicy() ||
        sync_disabled_by_admin_) {
      result.Put(DISABLE_REASON_ENTERPRISE_POLICY);
    }
    if (!IsSignedIn()) {
      result.Put(DISABLE_REASON_NOT_SIGNED_IN);
    }
  }

  if (unrecoverable_error_reason_) {
    result.Put(DISABLE_REASON_UNRECOVERABLE_ERROR);
  }
  return result;
}

SyncService::TransportState SyncServiceImpl::GetTransportState() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!GetDisableReasons().empty()) {
    // Note: we generally shouldn't have an engine while in a disabled state,
    // but it can happen if this method gets called during ResetEngine().
    return TransportState::DISABLED;
  }

  if (auth_manager_->IsSyncPaused()) {
    return TransportState::PAUSED;
  }

  CHECK(IsEngineAllowedToRun());

  if (!engine_) {
    // Starting the engine is allowed but didn't happen. There are three
    // possible scenarios:
    // 1) Startup was deferred, in which case it can take noticeably long until
    //  . the engine initializes. This case can be distinguished by checking if
    //  . `deferring_first_start_since_` is set.
    // 2) Startup is about to happen because SyncServiceImpl::TryStart() was
    //  . invoked, but the posted task to run SyncServiceImpl::TryStartImpl()
    //  . hasn't been processed yet.
    // 3) The service is shutting down.
    //
    // This function reports TransportState::START_DEFERRED only for the first,
    // which is the only real deferred case.
    return deferring_first_start_since_.is_null()
               ? TransportState::INITIALIZING
               : TransportState::START_DEFERRED;
  }

  if (!engine_->IsInitialized() || !data_type_manager_) {
    return TransportState::INITIALIZING;
  }

  // Determining the account's managed-ness status is also considered part of
  // initialization.
  if (!IsLocalSyncEnabled() &&
      auth_manager_->GetActiveAccountInfo().managed_status ==
          signin::AccountManagedStatusFinderOutcome::kPending) {
    return TransportState::INITIALIZING;
  }

  // At this point we should usually be able to configure our data types (so the
  // DataTypeManager should not be STOPPED anymore), unless setup is in
  // progress. But it can also happen if this gets called from DataTypeManager
  // itself.
  if (data_type_manager_->state() == DataTypeManager::STOPPED) {
    return TransportState::PENDING_DESIRED_CONFIGURATION;
  }

  if (data_type_manager_->state() != DataTypeManager::CONFIGURED) {
    return TransportState::CONFIGURING;
  }

  return TransportState::ACTIVE;
}

SyncService::UserActionableError SyncServiceImpl::GetUserActionableError()
    const {
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
  if (HasSyncConsent()) {
    if (!GetUserSettings()->IsInitialSyncFeatureSetupComplete()) {
      return UserActionableError::kNeedsSettingsConfirmation;
    }
    // UPGRADE_CLIENT error is unrecoverable, but is treated separately below.
    if (HasUnrecoverableError() &&
        last_actionable_error_.action != UPGRADE_CLIENT) {
      return UserActionableError::kUnrecoverableError;
    }
  }
#endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)

  if (GetAuthError().state() != GoogleServiceAuthError::NONE) {
#if BUILDFLAG(IS_IOS)
    if (GetAuthError().state() ==
            GoogleServiceAuthError::DEVICE_MANAGEMENT_ERROR &&
        base::FeatureList::IsEnabled(
            switches::kHandleMdmErrorsForDasherAccounts)) {
      return UserActionableError::kDeviceManagementError;
    }
#endif  // BUILDFLAG(IS_IOS)
    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)

  // This error should ideally be the last one to be checked. Any new identity
  // errors should be handled before this.
  const DataTypeStatusTable::TypeErrorMap data_type_errors =
      data_type_manager_->GetDataTypeErrors();
  auto it = data_type_errors.find(BOOKMARKS);
  if (it != data_type_errors.end() &&
      bookmark_sync_error_state_.IsActionableError(it->second)) {
    return UserActionableError::kBookmarksLimitExceeded;
  }

  return UserActionableError::kNone;
}

void SyncServiceImpl::NotifyObservers() {
  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(ConfigureReason::kExistingClientRestart,
                           /*bypass_setup_in_progress_check=*/false);
}

void SyncServiceImpl::OnSyncCycleCompleted(const SyncCycleSnapshot& snapshot) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(engine_);
  CHECK(engine_->IsInitialized());

  last_snapshot_ = snapshot;

  // Cache trusted vault debug info into prefs, to make it synchronously
  // available upon future profile startups. In most cases this will happen in
  // OnEngineInitialized(), but it may also happen that the information was just
  // populated server-side and downloaded, after (or long after) the engine is
  // initialized.
  CacheTrustedVaultDebugInfoToPrefsFromEngine();

  DVLOG(2) << "Notifying observers sync cycle completed";
  NotifySyncCycleCompleted();
}

void SyncServiceImpl::OnConnectionStatusChange(ConnectionStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!IsLocalSyncEnabled()) {
    auth_manager_->ConnectionStatusChanged(status);
  }
  DVLOG(2) << "Notify observers OnConnectionStatusChange";
  NotifyObservers();
}

void SyncServiceImpl::OnMigrationNeededForTypes(DataTypeSet types) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(engine_);
  DCHECK(engine_->IsInitialized());

  // Migrator must be valid, because we don't sync until it is created and this
  // callback originates from a sync cycle.
  migrator_->MigrateTypes(types);
}

void SyncServiceImpl::OnActionableProtocolError(
    const SyncProtocolError& error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  last_actionable_error_ = error;
  DCHECK_NE(last_actionable_error_.action, UNKNOWN_ACTION);
  switch (error.action) {
    case UPGRADE_CLIENT:
      if (IsSetupInProgress()) {
        StopAndClear(ResetEngineReason::kUpgradeClientError);
      }
      // Trigger an unrecoverable error to stop syncing.
      OnUnrecoverableErrorImpl(FROM_HERE,
                               last_actionable_error_.error_description,
                               ERROR_REASON_ACTIONABLE_ERROR);
      break;
    case DISABLE_SYNC_ON_CLIENT:
      if (error.error_type == NOT_MY_BIRTHDAY ||
          error.error_type == ENCRYPTION_OBSOLETE) {
        // Note: For legacy reasons, `kImplicitPassphrase` is used to represent
        // the "unknown" state.
        base::UmaHistogramEnumeration(
            "Sync.PassphraseTypeUponNotMyBirthdayOrEncryptionObsolete",
            crypto_.GetPassphraseType().value_or(
                PassphraseType::kImplicitPassphrase));
        // Account passphrase pref should be cleared when sync is reset from the
        // dashboard because then the cached passphrase wouldn't be useful
        // anymore.
        sync_prefs_.ClearEncryptionBootstrapTokenForAccount(
            GetAccountInfo().gaia);
      }

      // Security domain state might be reset, reset local state as well.
      sync_client_->GetTrustedVaultClient()->ClearLocalDataForAccount(
          GetAccountInfo());

      // Note: This method might get called again in the following code when
      // clearing the primary account. But due to rarity of the event, this
      // should be okay.
      StopAndClear(ResetEngineReason::kDisableSyncOnClient);

#if BUILDFLAG(IS_CHROMEOS)
      // On Ash, the primary account is always set and sync the feature
      // turned on, so a dedicated bit is needed to ensure that
      // Sync-the-feature remains off. Note that sync-the-transport will restart
      // immediately because IsEngineAllowedToRun() is almost certainly true at
      // this point and StopAndClear() leads to TryStart().
      user_settings_->SetSyncFeatureDisabledViaDashboard();
#else  // !BUILDFLAG(IS_CHROMEOS)
      // On every platform except ash, revoke the Sync consent/Clear primary
      // account after a dashboard clear.
      // TODO(crbug.com/40066949): Simplify once kSync becomes unreachable or is
      // deleted from the codebase. See ConsentLevel::kSync documentation for
      // details.
      if (!IsLocalSyncEnabled() &&
          identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
        signin::PrimaryAccountMutator* account_mutator =
            identity_manager_->GetPrimaryAccountMutator();
        // GetPrimaryAccountMutator() returns nullptr on ChromeOS only.
        DCHECK(account_mutator);

        // TODO(crbug.com/40220945): make the behaviour consistent across
        // platforms. Any platforms which support a single-step flow that signs
        // in and enables sync should clear the primary account here for
        // symmetry.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
        // On mobile, fully sign out the user (clear the primary account) but
        // do not remove the list of known accounts, as the user may sign in
        // again.
        account_mutator->RemovePrimaryAccountButKeepTokens(
            signin_metrics::ProfileSignout::kServerForcedDisable);
#else
        // The Sync consent will be revoked, and Sync will enter
        // Sync-the-transport mode.
        if (base::FeatureList::IsEnabled(
                kSyncCopyPreferencesToTransportModeOnServerForcedDisable)) {
          CHECK(!GetAccountInfo().gaia.empty());
          // Migrating the user's selected types prefs to ensure that they will
          // be available in Sync-the-transport mode.
          SyncPrefs::MigrateGlobalDataTypePrefsToAccount(
              sync_client_->GetPrefService(), GetAccountInfo().gaia);
        }
        // Note: On some platforms, revoking the sync consent will also clear
        // the primary account as transitioning from ConsentLevel::kSync to
        // ConsentLevel::kSignin is not supported.
        account_mutator->RevokeSyncConsent(
            signin_metrics::ProfileSignout::kServerForcedDisable);
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
      }
#endif  // BUILDFLAG(IS_CHROMEOS)
      break;
    case STOP_SYNC_FOR_DISABLED_ACCOUNT:
      // Sync disabled by domain admin. Stop syncing until next restart.
      sync_disabled_by_admin_ = true;
      ResetEngine(ResetEngineReason::kDisabledAccount);
      break;
    case RESET_LOCAL_SYNC_DATA:
      ResetEngine(ResetEngineReason::kResetLocalData);
      break;
    case UNKNOWN_ACTION:
      NOTREACHED();
  }
  DVLOG(2) << "Notify observers OnActionableProtocolError";
  NotifyObservers();
}

void SyncServiceImpl::OnBackedOffTypesChanged() {
  DVLOG(2) << "Notify observers OnBackedOffTypesChanged";
  NotifyObservers();
}

void SyncServiceImpl::OnInvalidationStatusChanged() {
  DVLOG(2) << "Notify observers OnInvalidationStatusChanged";
  NotifyObservers();
}

void SyncServiceImpl::OnNewInvalidatedDataTypes() {
  DVLOG(2) << "Notify observers OnNewInvalidatedDataTypes";
  NotifyObservers();
}

void SyncServiceImpl::FetchAccessToken(
    base::OnceCallback<void(signin::AccessTokenInfo)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!auth_manager_) {
    std::move(callback).Run(signin::AccessTokenInfo());
    return;
  }

  auth_manager_->FetchAccessToken(std::move(callback));
}

void SyncServiceImpl::OnConfigureDone(
    const DataTypeManager::ConfigureResult& result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DVLOG(1) << "SyncServiceImpl::OnConfigureDone called with status: "
           << result.status;
  switch (result.status) {
    case DataTypeManager::ABORTED:
      // Configuration was aborted. This is not an error, if initiated by user.
      DVLOG(0) << "SyncServiceImpl sync configuration aborted";
      return;
    case DataTypeManager::OK:
      // Some or all types succeeded.
      break;
  }

  base::UmaHistogramBoolean("Sync.ConfigureDataTypeManager.Finished",
                            is_first_time_sync_configure_);

  // We should never get in a state where we have no encrypted datatypes
  // enabled, and yet we still think we require a passphrase for decryption.
  DCHECK(!user_settings_->IsPassphraseRequiredForPreferredDataTypes() ||
         user_settings_->IsEncryptedDatatypePreferred());

  DVLOG(2) << "Notify observers OnConfigureDone";
  NotifyObservers();

  // Update configured data types and start handling incoming invalidations. The
  // order is important to guarantee that data types are configured to prevent
  // filtering out invalidations.
  UpdateDataTypesForInvalidations();
  engine_->StartHandlingInvalidations();

  if (migrator_.get() && migrator_->state() != BackendMigrator::IDLE) {
    // Migration in progress.  Let the migrator know we just finished
    // configuring something.  It will be up to the migrator to call
    // StartSyncingWithServer() if migration is now finished.
    migrator_->OnConfigureDone(result);
    return;
  }

  StartSyncingWithServer();
}

void SyncServiceImpl::OnConfigureStart() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  engine_->StartConfiguration();
  DVLOG(2) << "Notify observers OnConfigureStart";
  NotifyObservers();
}

void SyncServiceImpl::SyncAuthAccountStateChanged() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!IsSignedIn()) {
    // The account was signed out, so shut down.
    sync_disabled_by_admin_ = false;
    StopAndClear(ResetEngineReason::kNotSignedIn);
    DCHECK(!engine_);
  } else {
    // Either a new account was signed in, or the existing account's
    // `is_sync_consented` and/or `managed_status` have changed. Start up or
    // reconfigure.
    if (!engine_) {
      // If sync startup is still deferred, then honor that. (In practice, this
      // mostly happens when the `managed_status` of the account gets
      // determined.)
      if (deferring_first_start_since_.is_null()) {
        TryStart();
      }
      NotifyObservers();
    } else {
      ConfigureDataTypeManager(ConfigureReason::kReconfiguration,
                               /*bypass_setup_in_progress_check=*/false);
    }
  }
}

void SyncServiceImpl::SyncAuthCredentialsChanged() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Cache in prefs whether a persistent auth error exists.
  if (auth_manager_->IsSyncPaused()) {
    sync_prefs_.SetHasCachedPersistentAuthErrorForMetrics(true);
  } else if (!auth_manager_->GetCredentials().access_token_info.token.empty()) {
    if (!IsSyncFeatureEnabled() &&
        sync_prefs_.HasCachedPersistentAuthErrorForMetrics()) {
      // An auth error is being fixed while in transport mode. Record the amount
      // of unsynced data in histograms.
      GetTypesWithUnsyncedData(
          TypesRequiringUnsyncedDataCheckOnSignout(),
          base::BindOnce(
              &SyncRecordDataTypeNumUnsyncedEntitiesFromDataCounts,
              UnsyncedDataRecordingEvent::kOnReauthFromPendingState));
    }

    // In order to conclude with certainty that there is no persistent auth
    // error, it is necessary to get an access token successfully.
    sync_prefs_.SetHasCachedPersistentAuthErrorForMetrics(false);
  }

  // If the engine isn't allowed to start anymore due to the credentials change,
  // then shut down. This happens when there is a persistent auth error (e.g.
  // the user signs out on the web), which implies the "Sync paused" state.
  if (!IsEngineAllowedToRun()) {
    // If the engine currently exists, then ResetEngine() will notify observers
    // anyway. Otherwise, notify them here. (One relevant case is when entering
    // the PAUSED state before the engine was created, e.g. during deferred
    // startup.)
    if (!engine_) {
      DVLOG(2) << "Notify observers on credentials changed";
      NotifyObservers();
    }
    ResetEngine(ResetEngineReason::kCredentialsChanged);
    return;
  }

  if (!engine_) {
    TryStart();
  } else if (!base::FeatureList::IsEnabled(kSyncUsePropagatedAccessToken)) {
    // When kSyncUsePropagatedAccessToken is enabled, access tokens are fetched
    // on demand and propagated via SyncCycle when needed rather than cached in
    // the network sync layer.
    // If the engine already exists, just propagate the new credentials.
    SyncCredentials credentials = auth_manager_->GetCredentials();
    if (credentials.access_token_info.token.empty()) {
      engine_->InvalidateCredentials();
    } else {
      engine_->UpdateCredentials(credentials);
    }
  }

  DVLOG(2) << "Notify observers on credentials changed";
  NotifyObservers();
}

void SyncServiceImpl::CryptoStateChanged() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DVLOG(2) << "Notify observers on CryptoStateChanged";
  NotifyObservers();
}

void SyncServiceImpl::CryptoRequiredUserActionChanged() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  MaybeRecordTrustedVaultHistograms();
}

void SyncServiceImpl::MaybeRecordTrustedVaultHistograms() {
  if (should_record_trusted_vault_error_shown_on_startup_ &&
      crypto_.IsTrustedVaultKeyRequiredStateKnown() &&
      user_settings_->IsEncryptedDatatypePreferred()) {
    // If the key-required state is known, the engine must exist.
    DCHECK(engine_);

    should_record_trusted_vault_error_shown_on_startup_ = false;
    if (crypto_.GetPassphraseType() ==
        PassphraseType::kTrustedVaultPassphrase) {
      RecordTrustedVaultHistogramBooleanWithMigrationSuffix(
          "Sync.TrustedVaultErrorShownOnStartup",
          user_settings_->IsTrustedVaultKeyRequiredForPreferredDataTypes(),
          engine_->GetDetailedStatus());

      if (is_first_time_sync_configure_) {
        // A 'first time sync configure' is an indication that the account was
        // added to the browser recently (sign in).
        base::UmaHistogramBoolean(
            "Sync.TrustedVaultErrorShownOnFirstTimeSync2",
            user_settings_->IsTrustedVaultKeyRequiredForPreferredDataTypes());
      }
    }
  }
}

void SyncServiceImpl::ReconfigureDataTypesDueToCrypto() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  ConfigureDataTypeManager(ConfigureReason::kCrypto,
                           /*bypass_setup_in_progress_check=*/false);
}

void SyncServiceImpl::PassphraseTypeChanged(PassphraseType passphrase_type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  // If kReplaceSyncPromosWithSignInPromos is enabled, new users with custom
  // passphrase should have kAutofill disabled upon the initial sign-in. This is
  // done to prevent confusion, as addresses are NOT encrypted by the custom
  // passphrase
  //
  // This check is skipped on desktop (Windows, Mac, Linux) and ChromeOS because
  // the user interface on those platforms already clarifies this nuance about
  // address encryption.
  //
  // The first `PassphraseTypeChanged()` call reflects the server-side
  // passphrase type before signing in.
  if (!sync_prefs_.GetCachedPassphraseType().has_value() &&
      IsExplicitPassphrase(passphrase_type) &&
      GetSyncAccountStateForPrefs() ==
          SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent &&
      sync_prefs_.DoesTypeHaveDefaultValueForAccount(
          UserSelectableType::kAutofill, GetAccountInfo().gaia) &&
      IsReplaceSyncPromosWithSignInPromosEnabled()) {
    GetUserSettings()->SetSelectedType(UserSelectableType::kAutofill, false);
  }
#endif
  sync_prefs_.SetCachedPassphraseType(passphrase_type);
}

std::optional<PassphraseType> SyncServiceImpl::GetPassphraseType() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return sync_prefs_.GetCachedPassphraseType();
}

void SyncServiceImpl::SetEncryptionBootstrapToken(
    const CustomPassphraseBootstrapToken& bootstrap_token,
    const os_crypt_async::Encryptor& encryptor) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  user_settings_->SetEncryptionBootstrapToken(bootstrap_token, encryptor);
  SendExplicitPassphraseToPlatformClient();
}

CustomPassphraseBootstrapToken SyncServiceImpl::GetEncryptionBootstrapToken(
    const os_crypt_async::Encryptor& encryptor) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return user_settings_->GetEncryptionBootstrapToken(encryptor);
}

bool SyncServiceImpl::IsCustomPassphraseAllowed() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return sync_client_->IsCustomPassphraseAllowed();
}

SyncPrefs::SyncAccountState SyncServiceImpl::GetSyncAccountStateForPrefs()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (IsLocalSyncEnabled()) {
    // Local sync should behave like a syncing user.
    return SyncPrefs::SyncAccountState::kSyncing;
  }
  if (!IsSignedIn()) {
    return SyncPrefs::SyncAccountState::kNotSignedIn;
  }
  // This doesn't check IsSyncFeatureEnabled() so it covers the case of advanced
  // sync setup, where IsInitialSyncFeatureSetupComplete() is not true yet.
  return HasSyncConsent()
             ? SyncPrefs::SyncAccountState::kSyncing
             : SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent;
}

CoreAccountInfo SyncServiceImpl::GetSyncAccountInfoForPrefs() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return GetAccountInfo();
}

#if BUILDFLAG(IS_CHROMEOS)
void SyncServiceImpl::OnSyncFeatureDisabledViaDashboardCleared() {
  // If the Sync engine was already initialized (probably running in transport
  // mode), just reconfigure.
  if (engine_ && engine_->IsInitialized()) {
    ConfigureDataTypeManager(ConfigureReason::kReconfiguration,
                             /*bypass_setup_in_progress_check=*/false);
  } else {
    // Otherwise try to start up. Note that there might still be other disable
    // reasons remaining, in which case this will effectively do nothing.
    TryStart();
  }

  NotifyObservers();
}
#endif  // BUILDFLAG(IS_CHROMEOS)

bool SyncServiceImpl::IsSetupInProgress() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return outstanding_setup_in_progress_handles_ > 0;
}

bool SyncServiceImpl::QueryDetailedSyncStatusForDebugging(
    SyncStatus* result) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (engine_ && engine_->IsInitialized()) {
    *result = engine_->GetDetailedStatus();
    return true;
  }
  SyncStatus status;
  status.sync_protocol_error = last_actionable_error_;
  *result = status;
  return false;
}

GoogleServiceAuthError SyncServiceImpl::GetAuthError() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return auth_manager_->GetLastAuthError();
}

base::Time SyncServiceImpl::GetAuthErrorTime() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return auth_manager_->GetLastAuthErrorTime();
}

bool SyncServiceImpl::HasCachedPersistentAuthErrorForMetrics() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return sync_prefs_.HasCachedPersistentAuthErrorForMetrics();
}

std::unique_ptr<SyncSetupInProgressHandle>
SyncServiceImpl::GetSetupInProgressHandle() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (++outstanding_setup_in_progress_handles_ == 1) {
    TryStart();

    DVLOG(2) << "Notify observers GetSetupInProgressHandle";
    NotifyObservers();
  }

  return std::make_unique<SyncSetupInProgressHandle>(
      base::BindRepeating(&SyncServiceImpl::OnSetupInProgressHandleDestroyed,
                          weak_factory_.GetWeakPtr()));
}

bool SyncServiceImpl::IsLocalSyncEnabled() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return sync_prefs_.IsLocalSyncEnabled();
}

void SyncServiceImpl::TriggerRefresh(TriggerRefreshSource source,
                                     const DataTypeSet& types) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::UmaHistogramEnumeration("Sync.TriggerRefreshSource", source);
  if (engine_ && engine_->IsInitialized()) {
    engine_->TriggerRefresh(types);
  }
}

bool SyncServiceImpl::IsSignedIn() const {
  // Sync is logged in if there is a non-empty account id.
  return !GetAccountInfo().account_id.empty();
}

base::Time SyncServiceImpl::GetLastSyncedTimeForDebugging() const {
  if (!engine_ || !engine_->IsInitialized()) {
    return base::Time();
  }

  return engine_->GetLastSyncedTimeForDebugging();
}

void SyncServiceImpl::OnSelectedTypesChanged() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  ConfigureDataTypeManager(ConfigureReason::kReconfiguration,
                           /*bypass_setup_in_progress_check=*/false);
}

SyncClient* SyncServiceImpl::GetSyncClientForTest() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();
  return sync_client_.get();
}

void SyncServiceImpl::ReportDataTypeErrorForTest(DataType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();

  DataTypeController* controller =
      data_type_manager_->GetControllerForTest(type);  // IN-TEST
  CHECK(controller);
  controller->ReportBridgeErrorForTest();  // IN-TEST
}

void SyncServiceImpl::RunOrQueueTaskOnEngineInitializedForTest(
    base::OnceClosure task) {
  RunOrQueueTaskOnEngineInitialized(std::move(task));
}

void SyncServiceImpl::AddObserver(SyncServiceObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(observers_);
  observers_->AddObserver(observer);
}

void SyncServiceImpl::RemoveObserver(SyncServiceObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(observers_);
  observers_->RemoveObserver(observer);
}

bool SyncServiceImpl::HasObserver(const SyncServiceObserver* observer) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(observers_);
  return observers_->HasObserver(observer);
}

DataTypeSet SyncServiceImpl::GetPreferredDataTypes() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Some questionable callers exercise this function after Shutdown(). The
  // semantics aren't clear, but for cases like GetUploadToGoogleState() a
  // sensible behavior is to return an empty set.
  if (!data_type_manager_) {
    return DataTypeSet();
  }
  DataTypeSet types = user_settings_->GetPreferredDataTypes();
  // SyncUserSettings already filters out UserSelectableTypes that aren't
  // supported in transport mode. However, there are two reasons why the
  // DataTypes still need to be filtered here:
  // 1) For some UserSelectableTypes, some of their DataTypes are supported
  //    while others aren't.
  // 2) Some DataTypes implement additional preconditions in
  //    ShouldRunInTransportOnlyMode() (e.g. related to passphrase type).
  if (UseTransportOnlyMode()) {
    types = Intersection(
        types, data_type_manager_->GetDataTypesForTransportOnlyMode());
  }
  return types;
}

DataTypeSet SyncServiceImpl::GetDataTypesForTransportOnlyMode() const {
  if (!data_type_manager_) {
    return DataTypeSet();
  }

  return data_type_manager_->GetDataTypesForTransportOnlyMode();
}

DataTypeSet SyncServiceImpl::GetActiveDataTypes() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Return an empty set unless the TransportState is ACTIVE. (There may in fact
  // also be active types while it is CONFIGURING, but those are not exposed to
  // clients, mostly for historic reasons.)
  if (GetTransportState() != TransportState::ACTIVE) {
    return DataTypeSet();
  }
  CHECK(data_type_manager_);
  return data_type_manager_->GetActiveDataTypes();
}

DataTypeSet SyncServiceImpl::GetTypesWithPendingDownloadForInitialSync() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(data_type_manager_);

  if (GetTransportState() == TransportState::INITIALIZING &&
      !sync_client_->GetSyncEngineFactory()->HasTransportDataIncludingFirstSync(
          signin::GaiaIdHash::FromGaiaId(GetAccountInfo().gaia))) {
    // The engine is initializing for the very first sync (usually after
    // sign-in). In this case all types are reported as pending download,
    // optimistically assuming datatype preconditions will be met.
    return GetPreferredDataTypes();
  }

  return data_type_manager_->GetTypesWithPendingDownloadForInitialSync();
}

void SyncServiceImpl::ConfigureDataTypeManager(
    ConfigureReason reason,
    bool bypass_setup_in_progress_check) {
  // Don't configure datatypes if the setup UI is still on the screen - this
  // is to help multi-screen setting UIs (like iOS) where they don't want to
  // start syncing data until the user is done configuring encryption options,
  // etc. ConfigureDatatypeManager() will get called again once the last
  // SyncSetupInProgressHandle is released.
  if (!engine_ || !engine_->IsInitialized() ||
      (!bypass_setup_in_progress_check && IsSetupInProgress())) {
    // In any case, notify the observers. Whatever triggered the reconfigure
    // (attempt) might be interesting to them.
    NotifyObservers();
    return;
  }

  // If the account type hasn't been determined yet, don't configure. A
  // configuration will be triggered again once the type has been determined.
  if (!IsLocalSyncEnabled() &&
      auth_manager_->GetActiveAccountInfo().managed_status ==
          signin::AccountManagedStatusFinderOutcome::kPending) {
    return;
  }

  DCHECK(!engine_->GetCacheGuid().empty());
  DVLOG(1) << "Started DataTypeManager configuration, reason: "
           << static_cast<int>(reason);

  const bool use_transport_only_mode = UseTransportOnlyMode();

  ConfigureContext configure_context;
  configure_context.authenticated_gaia_id = GetAccountInfo().gaia;
  configure_context.account_managed_status =
      auth_manager_->GetActiveAccountInfo().managed_status;
  configure_context.cache_guid = engine_->GetCacheGuid();
  configure_context.sync_mode =
      use_transport_only_mode ? SyncMode::kTransportOnly : SyncMode::kFull;
  configure_context.reason = reason;
  configure_context.configuration_start_time = base::Time::Now();

  DCHECK(!configure_context.cache_guid.empty());

  if (!migrator_) {
    // We create the migrator at the same time.
    migrator_ = std::make_unique<BackendMigrator>(
        debug_identifier_, data_type_manager_.get(),
        base::BindRepeating(&SyncServiceImpl::ConfigureDataTypeManager,
                            base::Unretained(this), ConfigureReason::kMigration,
                            /*bypass_setup_in_progress_check=*/true),
        base::BindRepeating(&SyncServiceImpl::StartSyncingWithServer,
                            base::Unretained(this)));

    // Override reason if no configuration has completed ever.
    if (is_first_time_sync_configure_) {
      configure_context.reason = ConfigureReason::kNewClient;
    }
  }

  DCHECK(!configure_context.authenticated_gaia_id.empty() ||
         IsLocalSyncEnabled());
  DCHECK(!configure_context.cache_guid.empty());
  DCHECK_NE(configure_context.reason, ConfigureReason::kUnknown);

  data_type_manager_->Configure(GetPreferredDataTypes(), configure_context);

  // Record in UMA whether we're configuring the full Sync feature or only the
  // transport. These values are persisted to logs. Entries should not be
  // renumbered and numeric values should never be reused. Keep in sync with
  // SyncFeatureOrTransport in tools/metrics/histograms/metadata/sync/enums.xml.
  // LINT.IfChange(SyncFeatureOrTransport)
  enum class ConfigureDataTypeManagerOption {
    kFeature = 0,
    kTransport = 1,
    kMaxValue = kTransport
  };
  // LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncFeatureOrTransport)
  base::UmaHistogramEnumeration("Sync.ConfigureDataTypeManagerOption",
                                use_transport_only_mode
                                    ? ConfigureDataTypeManagerOption::kTransport
                                    : ConfigureDataTypeManagerOption::kFeature);

  base::UmaHistogramEnumeration("Sync.ConfigureDataTypeManagerReason",
                                configure_context.reason);

  base::UmaHistogramBoolean("Sync.ConfigureDataTypeManager.Start",
                            is_first_time_sync_configure_);

  // Record the user's choice of data types - in different ways depending on
  // whether Sync-the-feature is enabled (which uses "SyncEverything") or not
  // (which doesn't).
  if (use_transport_only_mode) {
    for (UserSelectableType type : user_settings_->GetSelectedTypes()) {
      DataTypeForHistograms canonical_data_type =
          DataTypeHistogramValue(UserSelectableTypeToCanonicalDataType(type));
      base::UmaHistogramEnumeration("Sync.SelectedTypesInTransportMode",
                                    canonical_data_type);
    }
  } else {
    bool sync_everything = user_settings_->IsSyncEverythingEnabled();
    base::UmaHistogramBoolean("Sync.SyncEverything2", sync_everything);

    if (!sync_everything) {
      for (UserSelectableType type : user_settings_->GetSelectedTypes()) {
        DataTypeForHistograms canonical_data_type =
            DataTypeHistogramValue(UserSelectableTypeToCanonicalDataType(type));
        base::UmaHistogramEnumeration("Sync.CustomSync3", canonical_data_type);
      }
    }

#if BUILDFLAG(IS_CHROMEOS)
    bool sync_everything_os = user_settings_->IsSyncAllOsTypesEnabled();
    base::UmaHistogramBoolean("Sync.SyncEverythingOS", sync_everything_os);
    if (!sync_everything_os) {
      for (UserSelectableOsType type : user_settings_->GetSelectedOsTypes()) {
        DataTypeForHistograms canonical_data_type = DataTypeHistogramValue(
            UserSelectableOsTypeToCanonicalDataType(type));
        base::UmaHistogramEnumeration("Sync.CustomOSSync", canonical_data_type);
      }
    }
#endif  // BUILDFLAG(IS_CHROMEOS)
  }

  NotifyObservers();
}

bool SyncServiceImpl::UseTransportOnlyMode() const {
  // Note: When local Sync is enabled, then we want full-sync mode (not just
  // transport), even though Sync-the-feature is not considered enabled.
  return !IsSyncFeatureEnabled() && !IsLocalSyncEnabled();
}

void SyncServiceImpl::UpdateDataTypesForInvalidations() {
  // Wait for configuring data types. This is needed to consider proxy types
  // which become known during configuration.
  if (!data_type_manager_ ||
      data_type_manager_->state() != DataTypeManager::CONFIGURED) {
    return;
  }

  // No need to register invalidations for non-protocol or commit-only types.
  DataTypeSet types = Intersection(GetPreferredDataTypes(), ProtocolTypes());
  types.RemoveAll(CommitOnlyTypes());

  bool should_register_sessions = sessions_invalidations_enabled_;
#if BUILDFLAG(IS_ANDROID)
  if (!should_register_sessions &&
      base::FeatureList::IsEnabled(
          kAlwaysRegisterSessionsInvalidationsAndroid)) {
    should_register_sessions = true;
  }
#endif
  if (!should_register_sessions) {
    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
}

TypeStatusMapForDebugging SyncServiceImpl::GetTypeStatusMapForDebugging()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!engine_ || !engine_->IsInitialized()) {
    return TypeStatusMapForDebugging();
  }

  const SyncStatus& detailed_status = engine_->GetDetailedStatus();
  return data_type_manager_->GetTypeStatusMapForDebugging(
      detailed_status.throttled_types, detailed_status.backed_off_types);
}

void SyncServiceImpl::GetEntityCountsForDebugging(
    base::RepeatingCallback<void(const TypeEntitiesCount&)> callback) const {
  return data_type_manager_->GetEntityCountsForDebugging(std::move(callback));
}

void SyncServiceImpl::OnSyncClientDisabledByPolicyChanged() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Local sync is not controlled by the "sync managed" policy, so these pref
  // changes make no difference to the service state.
  if (IsLocalSyncEnabled()) {
    return;
  }

  if (user_settings_->IsSyncClientDisabledByPolicy()) {
    StopAndClear(ResetEngineReason::kEnterprisePolicy);
#if BUILDFLAG(IS_CHROMEOS)
    // Disable OS data types to avoid automatic local data upload upon policy
    // removal, as OS data types do not support dual storage with UNO.
    if (!HasSyncConsent() && IsReplaceSyncPromosWithSignInPromosEnabled()) {
      user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/false,
                                         UserSelectableOsTypeSet());
    } else {
      // On ChromeOS Ash, sync-the-feature stays disabled even after the policy
      // is removed, for historic reasons. It is unclear if this behavior is
      // optional, because it is indistinguishable from the
      // sync-reset-via-dashboard case. It can be resolved by invoking
      // ClearSyncFeatureDisabledViaDashboard().
      user_settings_->SetSyncFeatureDisabledViaDashboard();
    }
#endif  // BUILDFLAG(IS_CHROMEOS)
  } else {
    // Sync is no longer disabled by policy. Try starting it up if appropriate.
    DCHECK(!engine_);
    TryStart();
    DVLOG(2) << "Notify observers OnSyncManagedPrefChange";
    NotifyObservers();
  }
}

#if !BUILDFLAG(IS_CHROMEOS)
void SyncServiceImpl::OnInitialSyncFeatureSetupCompleted() {
  ConfigureDataTypeManager(ConfigureReason::kReconfiguration,
                           /*bypass_setup_in_progress_check=*/false);
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

void SyncServiceImpl::OnAccountsCookieDeletedByUserAction() {
  // Pass an empty `signin::AccountsInCookieJarInfo` to simulate empty cookies.
  MaybeClearAccountKeyedPreferences(
      identity_manager_, signin::AccountsInCookieJarInfo(), *user_settings_);
}

void SyncServiceImpl::OnAccountsInCookieUpdated(
    const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
    const GoogleServiceAuthError& error) {
  OnAccountsInCookieUpdatedWithCallback(accounts_in_cookie_jar_info,
                                        base::NullCallback());
}

void SyncServiceImpl::OnPrimaryAccountChanged(
    const signin::PrimaryAccountChangeEvent& event_details) {
  // When setting the primary account (at either ConsentLevel), record metrics.
  for (signin::ConsentLevel consent_level :
       {signin::ConsentLevel::kSignin, signin::ConsentLevel::kSync}) {
    switch (event_details.GetEventTypeFor(consent_level)) {
      case signin::PrimaryAccountChangeEvent::Type::kNone:
      case signin::PrimaryAccountChangeEvent::Type::kCleared:
        break;
      case signin::PrimaryAccountChangeEvent::Type::kSet:
        CHECK(event_details.GetSetPrimaryAccountAccessPoint().has_value());
        signin_metrics::AccessPoint access_point =
            event_details.GetSetPrimaryAccountAccessPoint().value();

        // The history opt-in state can only be queried after sync's internal
        // account state has been updated. That may or may not have happened
        // yet; depends on the order of IdentityManager observers
        // (SyncServiceImpl vs SyncAuthManager).
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(
                &SyncServiceImpl::RecordHistoryOptInStateOnSigninHistograms,
                weak_factory_.GetWeakPtr(), access_point, consent_level));
    }
  }

  if (event_details.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
      signin::PrimaryAccountChangeEvent::Type::kCleared) {
    MaybeClearAccountKeyedPreferences(
        identity_manager_, identity_manager_->GetAccountsInCookieJar(),
        *user_settings_);
  }
}

void SyncServiceImpl::OnIdentityManagerShutdown(
    signin::IdentityManager* identity_manager) {
  // Needs to be shutdown before IdentityManager.
  NOTREACHED(base::NotFatalUntil::M142);
}

bool SyncServiceImpl::IsDeviceStatisticsMetricReportingEnabled() {
  return sync_client_->IsMetricsAndCrashReportingEnabled();
}

std::unique_ptr<DeviceStatisticsRequest>
SyncServiceImpl::CreateDeviceStatisticsRequest(const CoreAccountInfo& account,
                                               const GURL& url) {
  return std::make_unique<DeviceStatisticsRequestImpl>(
      sync_client_->GetIdentityManager(), url_loader_factory_,
      MakeUserAgentForSync(channel_), account, url);
}

base::flat_set<std::string>
SyncServiceImpl::GetCurrentDeviceCacheGuidsForAllGaiaIds() const {
  return SyncTransportDataPrefs::GetCurrentDeviceCacheGuidsForAllGaiaIds(
      sync_client_->GetPrefService());
}

base::flat_set<std::string>
SyncServiceImpl::GetCurrentDeviceCacheGuidsForDeviceStatistics() {
  return GetCurrentDeviceCacheGuidsForAllGaiaIds();
}

void SyncServiceImpl::OnAccountsInCookieUpdatedWithCallback(
    const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
    base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  MaybeClearAccountKeyedPreferences(
      identity_manager_, accounts_in_cookie_jar_info, *user_settings_);

  if (!engine_ || !engine_->IsInitialized()) {
    return;
  }

  const bool cookie_jar_mismatch = HasCookieJarMismatch(
      accounts_in_cookie_jar_info.GetPotentiallyInvalidSignedInAccounts());
  const bool cookie_jar_empty =
      accounts_in_cookie_jar_info.GetPotentiallyInvalidSignedInAccounts()
          .empty();

  DVLOG(1) << "Cookie jar mismatch: " << cookie_jar_mismatch;
  DVLOG(1) << "Cookie jar empty: " << cookie_jar_empty;
  engine_->OnCookieJarChanged(cookie_jar_mismatch, std::move(callback));
}

bool SyncServiceImpl::HasCookieJarMismatch(
    const std::vector<gaia::ListedAccount>& cookie_jar_accounts) {
  CoreAccountId account_id = GetAccountInfo().account_id;
  // Iterate through list of accounts, looking for current sync account.
  for (const gaia::ListedAccount& account : cookie_jar_accounts) {
    if (account.id == account_id) {
      return false;
    }
  }
  return true;
}

void SyncServiceImpl::AddProtocolEventObserver(
    ProtocolEventObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  protocol_event_observers_.AddObserver(observer);
  if (engine_) {
    engine_->RequestBufferedProtocolEventsAndEnableForwarding();
  }
}

void SyncServiceImpl::RemoveProtocolEventObserver(
    ProtocolEventObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  protocol_event_observers_.RemoveObserver(observer);
  if (engine_ && protocol_event_observers_.empty()) {
    engine_->DisableProtocolEventForwarding();
  }
}

void SyncServiceImpl::GetAllNodesForDebugging(
    base::OnceCallback<void(base::ListValue)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  data_type_manager_->GetAllNodesForDebugging(std::move(callback));
}

SyncService::DataTypeDownloadStatus SyncServiceImpl::GetDownloadStatusFor(
    DataType type) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Download status doesn't make sense for non-real data types.
  CHECK(IsRealDataType(type));

  if (!IsLocalSyncEnabled()) {
    // TODO(crbug.com/40260679): Verify whether it's actually necessary to check
    // IsActiveAccountInfoFullyLoaded() - can the engine actually start, and
    // data types become active, if that isn't true?
    if (!auth_manager_->IsActiveAccountInfoFullyLoaded()) {
      DVLOG(1) << "Waiting for refresh tokens to be loaded from the disk";
      // GetDisableReasons() won't be empty until then.
      return DataTypeDownloadStatus::kWaitingForUpdates;
    }

    if (auth_manager_->IsSyncPaused()) {
      DVLOG(1) << "Error download status because sync is paused";
      return DataTypeDownloadStatus::kError;
    }
  }

  // TODO(crbug.com/40260679): check whether this works when local sync is
  // enabled.
  if (!GetDisableReasons().empty() || !GetPreferredDataTypes().Has(type)) {
    DVLOG(1)
        << "Sync or " << DataTypeToDebugString(type)
        << " is disabled hence updates won't be downloaded from the server";
    return DataTypeDownloadStatus::kError;
  }

  if (!engine_ || !engine_->IsInitialized()) {
    DVLOG(1) << "Waiting for the sync engine to be fully initialized";
    return DataTypeDownloadStatus::kWaitingForUpdates;
  }

  if (data_type_manager_->GetDataTypesWithPermanentErrors().Has(type)) {
    DVLOG(1) << "Permanent error for " << DataTypeToDebugString(type);
    return DataTypeDownloadStatus::kError;
  }

  if (!GetActiveDataTypes().Has(type)) {
    DVLOG(1) << "Data type is not active yet";
    return DataTypeDownloadStatus::kWaitingForUpdates;
  }

  if (!engine_->GetDetailedStatus().notifications_enabled) {
    DVLOG(1) << "Waiting for invalidations to be initialized";
    return DataTypeDownloadStatus::kWaitingForUpdates;
  }

  // If there are any incoming invalidations or poll time elapsed, there can be
  // new updates to download from the server.
  if (engine_->GetDetailedStatus().invalidated_data_types.Has(type)) {
    DVLOG(1) << "There are incoming invalidations for: "
             << DataTypeToDebugString(type);
    return DataTypeDownloadStatus::kWaitingForUpdates;
  }

  // Wait for the poll request only during browser startup (i.e. when there were
  // not completed sync cycles). IsNextPollTimeInThePast() uses base::Time which
  // while poll scheduler uses base::TimeTicks. They may diverge in sleep mode
  // (TimeTicks may be paused) and it's possible that the actual timer for
  // polling will take longer. This might result in a long-standing
  // `kWaitingForUpdates` status.
  if (!HasCompletedSyncCycle() && engine_->IsNextPollTimeInThePast()) {
    DVLOG(1) << "Waiting for updates due an upcoming poll request";
    return DataTypeDownloadStatus::kWaitingForUpdates;
  }

  return DataTypeDownloadStatus::kUpToDate;
}

void SyncServiceImpl::CacheTrustedVaultDebugInfoToPrefsFromEngine() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(engine_);
  CHECK(engine_->IsInitialized());

  sync_prefs_.SetCachedTrustedVaultAutoUpgradeExperimentGroup(
      engine_->GetDetailedStatus()
          .trusted_vault_debug_info.auto_upgrade_experiment_group());

  RegisterTrustedVaultSyntheticFieldTrialsIfNecessary();
}

CoreAccountInfo SyncServiceImpl::GetAccountInfo() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!auth_manager_) {
    // Some crashes on iOS (crbug.com/962384) suggest that SyncServiceImpl
    // gets called after it has been already shutdown. It's not clear why this
    // actually happens. We add this null check here to protect against such
    // crashes.
    return CoreAccountInfo();
  }
  return auth_manager_->GetActiveAccountInfo().account_info;
}

bool SyncServiceImpl::HasSyncConsent() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!auth_manager_) {
    // This is a precautionary check to be consistent with the check in
    // GetAccountInfo().
    return false;
  }
  return auth_manager_->GetActiveAccountInfo().is_sync_consented;
}

void SyncServiceImpl::SetInvalidationsForSessionsEnabled(bool enabled) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  sessions_invalidations_enabled_ = enabled;
#if BUILDFLAG(IS_ANDROID)
  if (base::FeatureList::IsEnabled(
          kAlwaysRegisterSessionsInvalidationsAndroid)) {
    return;
  }
#endif
  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;
  }

  CHECK(crypto_.GetEncryptor());
  CustomPassphraseBootstrapToken token =
      user_settings_->GetEncryptionBootstrapToken(*crypto_.GetEncryptor());
  if (token.IsEmpty()) {
    return;
  }

  const sync_pb::NigoriKey& proto = token.ToProto();
  int32_t byte_size = proto.ByteSizeLong();
  std::vector<uint8_t> bytes(byte_size);
  proto.SerializeToArray(bytes.data(), byte_size);
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_ExplicitPassphrasePlatformClient_setExplicitDecryptionPassphrase(
      env, GetAccountInfo(), base::android::ToJavaByteArray(env, bytes));
#endif  // BUILDFLAG(IS_ANDROID)
}

void SyncServiceImpl::StopAndClear(ResetEngineReason reset_engine_reason) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  ClearUnrecoverableError();
  ResetEngine(reset_engine_reason);

  // For explicit passphrase users, clear the encryption key, such that they
  // will need to reenter it if sync gets re-enabled. Note: the gaia-keyed
  // passphrase pref should be cleared before clearing
  // InitialSyncFeatureSetupComplete().
  sync_prefs_.ClearAllEncryptionBootstrapTokens();
#if !BUILDFLAG(IS_CHROMEOS)
  // Note: ResetEngine() does *not* clear directly user-controlled prefs (such
  // as the set of selected types), so that if the user ever chooses to enable
  // Sync again, they start off with their previous settings by default.
  // However, they do have to go through the initial setup again.
  sync_prefs_.ClearInitialSyncFeatureSetupComplete();
#endif  // !BUILDFLAG(IS_CHROMEOS)
  sync_prefs_.ClearPassphrasePromptMutedProductVersion();
  // Cached information provided by SyncEngine must be cleared.
  sync_prefs_.ClearCachedPassphraseType();
  sync_prefs_.ClearCachedTrustedVaultAutoUpgradeExperimentGroup();
  // If the migration didn't finish before StopAndClear() was called, mark it as
  // done so it doesn't trigger again if the user signs in later.
  sync_prefs_.MarkPartialSyncToSigninMigrationFullyDone();

  if (reset_engine_reason == ResetEngineReason::kNotSignedIn) {
    sync_prefs_.ClearCachedPersistentAuthErrorForMetrics();
  }

  // Also let observers know that Sync-the-feature is now fully disabled
  // (before it possibly starts up again in transport-only mode).
  DVLOG(2) << "Notify observers on StopAndClear";
  NotifyObservers();
}

bool SyncServiceImpl::IsRetryingAccessTokenFetchForTest() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();
  return auth_manager_->IsRetryingAccessTokenFetchForTest();  // IN-TEST
}

std::string SyncServiceImpl::GetAccessTokenForTest() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_IS_TEST();
  return auth_manager_->GetCredentials().access_token_info.token;
}

SyncTokenStatus SyncServiceImpl::GetSyncTokenStatusForDebugging() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return auth_manager_->GetSyncTokenStatus();
}

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
void SyncServiceImpl::OverrideNetworkForTest(
    const CreateHttpPostProviderFactory& create_http_post_provider_factory_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // If the engine has already been created, then it has a copy of the previous
  // HttpPostProviderFactory creation callback. In that case, shut down and
  // recreate the engine, so that it uses the correct (overridden) callback.
  // This is a horrible hack; the proper fix would be to inject the
  // callback in the ctor instead of adding it retroactively.
  // Note that ResetEngine() can't be used here, because it would caues the
  // engine to immediately restart.
  // TODO(crbug.com/41451146): Clean this up and inject required upon
  // construction.
  bool restart = false;
  if (engine_) {
    engine_->StopSyncingForShutdown();

    data_type_manager_->Stop(SyncStopMetadataFate::KEEP_METADATA);
    data_type_manager_->SetConfigurer(nullptr);

    migrator_.reset();

    crypto_.Reset();

    engine_->Shutdown(ShutdownReason::STOP_SYNC_AND_KEEP_DATA);
    engine_.reset();

    auth_manager_->ConnectionClosed();

    restart = true;
  }
  DCHECK(!engine_);

  // If a previous request (with the wrong callback) already failed, the next
  // one would be backed off, which breaks tests. So reset the backoff.
  auth_manager_->ResetRequestAccessTokenBackoffForTest();  // IN-TEST

  // The null callback allows tests to easily reset to the default (real)
  // callback.
  create_http_post_provider_factory_override_for_test_ =
      create_http_post_provider_factory_cb
          ? std::make_optional(create_http_post_provider_factory_cb)
          : std::nullopt;

  if (restart) {
    TryStart();
  }
}
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)

SyncEncryptionHandler::Observer*
SyncServiceImpl::GetEncryptionObserverForTest() {
  CHECK_IS_TEST();
  return &crypto_;
}

void SyncServiceImpl::RemoveClientFromServer() const {
  if (!engine_ || !engine_->IsInitialized()) {
    return;
  }
  const std::string cache_guid = engine_->GetCacheGuid();
  const std::string birthday = engine_->GetBirthday();
  DCHECK(!cache_guid.empty());
  const std::string& access_token =
      auth_manager_->GetCredentials().access_token_info.token;
  const bool report_sync_stopped = !access_token.empty() && !birthday.empty();
  if (report_sync_stopped) {
    sync_stopped_reporter_->ReportSyncStopped(access_token, cache_guid,
                                              birthday);
  }
}

void SyncServiceImpl::RecordHistoryOptInStateOnSigninHistograms(
    signin_metrics::AccessPoint access_point,
    signin::ConsentLevel consent_level) {
  signin_metrics::RecordHistoryOptInStateOnSignin(
      access_point, consent_level,
      user_settings_->GetSelectedTypes().Has(UserSelectableType::kHistory));
}

const GURL& SyncServiceImpl::GetSyncServiceUrlForDebugging() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return sync_service_url_;
}

std::string SyncServiceImpl::GetUnrecoverableErrorMessageForDebugging() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return unrecoverable_error_message_;
}

base::Location SyncServiceImpl::GetUnrecoverableErrorLocationForDebugging()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return unrecoverable_error_location_;
}

void SyncServiceImpl::OnSetupInProgressHandleDestroyed() {
  DCHECK_GT(outstanding_setup_in_progress_handles_, 0);

  --outstanding_setup_in_progress_handles_;

  // The user closed a setup UI, and will expect their changes to actually
  // take effect now. So we reconfigure here even if another setup UI happens
  // to be open right now.
  ConfigureDataTypeManager(ConfigureReason::kReconfiguration,
                           /*bypass_setup_in_progress_check=*/true);
}

void SyncServiceImpl::RunOrQueueTaskOnEngineInitialized(
    base::OnceClosure task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (engine_ && engine_->IsInitialized()) {
    std::move(task).Run();
    return;
  }
  tasks_waiting_for_engine_initialization_.push_back(std::move(task));
}

void SyncServiceImpl::GetTypesWithUnsyncedData(
    DataTypeSet requested_types,
    base::OnceCallback<void(absl::flat_hash_map<DataType, size_t>)> callback)
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Syncing users do not use separate local and account storages. Thus, there's
  // no local-only data. The same is true for local sync users.
  if (HasSyncConsent() || IsLocalSyncEnabled()) {
    std::move(callback).Run({});
    return;
  }

  data_type_manager_->GetTypesWithUnsyncedData(requested_types,
                                               std::move(callback));
}

void SyncServiceImpl::GetLocalDataDescriptions(
    DataTypeSet types,
    base::OnceCallback<void(std::map<DataType, LocalDataDescription>)>
        callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Some code paths in GetLocalDataDescriptionsImpl() are synchronous, e.g.
  // if `types` have synchronous DataTypeLocalDataBatchUploader implementations.
  // Having an API that is sometime sync and sometimes async can be unexpected
  // to the caller and lead to bugs such as crbug.com/361088051. To avoid those,
  // post a task here to ensure the call is always async.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&SyncServiceImpl::GetLocalDataDescriptionsImpl,
                     weak_factory_.GetWeakPtr(), types, std::move(callback)));
}

void SyncServiceImpl::GetLocalDataDescriptionsImpl(
    DataTypeSet types,
    base::OnceCallback<void(std::map<DataType, LocalDataDescription>)>
        callback) {
  // Syncing users do not use separate local and account storages. Thus, there's
  // no local-only data. The same is true for local sync users.
  if (HasSyncConsent() || IsLocalSyncEnabled()) {
    std::move(callback).Run({});
    return;
  }

  data_type_manager_->GetLocalDataDescriptions(types, std::move(callback));
}

void SyncServiceImpl::TriggerLocalDataMigration(DataTypeSet types) {
  for (DataType type : types) {
    base::UmaHistogramEnumeration("Sync.BatchUpload.Requests3",
                                  syncer::DataTypeHistogramValue(type));
  }

  // Syncing users do not use separate local and account storages. Thus, there's
  // no local-only data to migrate. The same is true for local sync users.
  if (HasSyncConsent() || IsLocalSyncEnabled()) {
    return;
  }

  return data_type_manager_->TriggerLocalDataMigration(types);
}

void SyncServiceImpl::TriggerLocalDataMigrationForItems(
    std::map<DataType, std::vector<LocalDataItemModel::DataId>> items) {
  for (const auto& [type, _] : items) {
    base::UmaHistogramEnumeration("Sync.BatchUpload.Requests3",
                                  syncer::DataTypeHistogramValue(type));
  }

  // Syncing users do not use separate local and account storages. Thus, there's
  // no local-only data to migrate. The same is true for local sync users.
  if (HasSyncConsent() || IsLocalSyncEnabled()) {
    return;
  }

  return data_type_manager_->TriggerLocalDataMigrationForItems(
      std::move(items));
}

void SyncServiceImpl::SelectTypeAndMigrateLocalDataItemsWhenActive(
    DataType data_type,
    std::vector<LocalDataItemModel::DataId> items) {
  if (IsLocalSyncEnabled()) {
    // This function should not have been called for local sync (i.e. enterprise
    // roaming profiles). However, as additional safeguard, the call is ignored
    // to avoid any unexpected side-effects.
    return;
  }

  CHECK(local_data_migration_item_queue_);
  CHECK(IsSignedIn());
  // Syncing users do not use separate local and account storages. Thus, there's
  // no local-only data to migrate. The sign in through the promo should not
  // have made the user consent to sync.
  CHECK(!HasSyncConsent());

  std::optional<UserSelectableType> user_selectable_type =
      GetUserSelectableTypeFromDataType(data_type);
  CHECK(user_selectable_type.has_value());

  // Do not proceed if the data type is managed for the account, disabled by
  // policy or not available in transport-only mode.
  if (GetUserSettings()->IsTypeManagedByPolicy(user_selectable_type.value()) ||
      HasDisableReason(SyncService::DISABLE_REASON_ENTERPRISE_POLICY) ||
      !data_type_manager_->GetDataTypesForTransportOnlyMode().Has(data_type)) {
    return;
  }

  // Unset the user's preference for account storage if they had explicitly been
  // using local storage before. This will enable account storage for the type
  // by its default value set in the sync prefs.
  if (!GetUserSettings()->GetSelectedTypes().Has(
          user_selectable_type.value())) {
    GetUserSettings()->ResetSelectedType(user_selectable_type.value());
  }

  // At this point, the type should be selected.
  CHECK(
      GetUserSettings()->GetSelectedTypes().Has(user_selectable_type.value()));

  // Move the item as soon as the sync service activates.
  local_data_migration_item_queue_
      ->TriggerLocalDataMigrationForItemsWhenTypeBecomesActive(
          data_type, std::move(items));
}

void SyncServiceImpl::AcknowledgeBookmarksLimitExceededError(
    BookmarksLimitExceededHelpClickedSource source) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::UmaHistogramEnumeration("Sync.BookmarksLimitExceededHelpClickedSource",
                                source);
  bookmark_sync_error_state_.AcknowledgeError();
}

}  // namespace syncer

#if BUILDFLAG(IS_ANDROID)
DEFINE_JNI(ExplicitPassphrasePlatformClient)
#endif
