普通文本  |  1305行  |  43.85 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/sync/profile_sync_service.h"

#include <stddef.h>
#include <map>
#include <ostream>
#include <set>
#include <utility>

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/string16.h"
#include "base/stringprintf.h"
#include "base/task.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/net/gaia/token_service.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/backend_migrator.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/data_type_controller.h"
#include "chrome/browser/sync/glue/data_type_manager.h"
#include "chrome/browser/sync/glue/session_data_type_controller.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/profile_sync_factory.h"
#include "chrome/browser/sync/signin_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/time_format.h"
#include "chrome/common/url_constants.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "content/common/notification_type.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/native_widget_types.h"

using browser_sync::ChangeProcessor;
using browser_sync::DataTypeController;
using browser_sync::DataTypeManager;
using browser_sync::SyncBackendHost;
using sync_api::SyncCredentials;

typedef GoogleServiceAuthError AuthError;

const char* ProfileSyncService::kSyncServerUrl =
    "https://clients4.google.com/chrome-sync";

const char* ProfileSyncService::kDevServerUrl =
    "https://clients4.google.com/chrome-sync/dev";

static const int kSyncClearDataTimeoutInSeconds = 60;  // 1 minute.

ProfileSyncService::ProfileSyncService(ProfileSyncFactory* factory,
                                       Profile* profile,
                                       const std::string& cros_user)
    : last_auth_error_(AuthError::None()),
      observed_passphrase_required_(false),
      passphrase_required_for_decryption_(false),
      passphrase_migration_in_progress_(false),
      factory_(factory),
      profile_(profile),
      cros_user_(cros_user),
      sync_service_url_(kDevServerUrl),
      backend_initialized_(false),
      is_auth_in_progress_(false),
      wizard_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
      unrecoverable_error_detected_(false),
      scoped_runnable_method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
      expect_sync_configuration_aborted_(false),
      clear_server_data_state_(CLEAR_NOT_STARTED) {
  registrar_.Add(this,
                 NotificationType::SYNC_DATA_TYPES_UPDATED,
                 Source<Profile>(profile));

  // By default, dev & chromium users will go to the development servers.
  // Dev servers have more features than standard sync servers.
  // Chrome stable and beta builds will go to the standard sync servers.
#if defined(GOOGLE_CHROME_BUILD)
  // GetVersionStringModifier hits the registry. See http://crbug.com/70380.
  base::ThreadRestrictions::ScopedAllowIO allow_io;
  // For stable, this is "". For dev, this is "dev". For beta, this is "beta".
  // For daily, this is "canary build".
  // For Linux Chromium builds, this could be anything depending on the
  // distribution, so always direct those users to dev server urls.
  // If this is an official build, it will always be one of the above.
  std::string channel = platform_util::GetVersionStringModifier();
  if (channel.empty() || channel == "beta") {
    sync_service_url_ = GURL(kSyncServerUrl);
  }
#endif

  tried_implicit_gaia_remove_when_bug_62103_fixed_ = false;
}

ProfileSyncService::~ProfileSyncService() {
  Shutdown(false);
}

bool ProfileSyncService::AreCredentialsAvailable() {
  if (IsManaged()) {
    return false;
  }

  // CrOS user is always logged in. Chrome uses signin_ to check logged in.
  if (!cros_user_.empty() || !signin_->GetUsername().empty()) {
    // TODO(chron): Verify CrOS unit test behavior.
    if (profile()->GetTokenService() &&
        profile()->GetTokenService()->HasTokenForService(
            GaiaConstants::kSyncService)) {
      return true;
    }
  }
  return false;
}

void ProfileSyncService::Initialize() {
  InitSettings();
  RegisterPreferences();

  // Watch the preference that indicates sync is managed so we can take
  // appropriate action.
  pref_sync_managed_.Init(prefs::kSyncManaged, profile_->GetPrefs(), this);

  // For now, the only thing we can do through policy is to turn sync off.
  if (IsManaged()) {
    DisableForUser();
    return;
  }

  RegisterAuthNotifications();

  // In Chrome, we integrate a SigninManager which works with the sync
  // setup wizard to kick off the TokenService. CrOS does its own plumbing
  // for the TokenService.
  if (cros_user_.empty()) {
    // Will load tokens from DB and broadcast Token events after.
    // Note: We rely on signin_ != NULL unless !cros_user_.empty().
    signin_.reset(new SigninManager());
    signin_->Initialize(profile_);
  }

  if (!HasSyncSetupCompleted()) {
    DisableForUser();  // Clean up in case of previous crash / setup abort.

    // Under ChromeOS, just autostart it anyway if creds are here and start
    // is not being suppressed by preferences.
    if (!cros_user_.empty() &&
        !profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart) &&
        AreCredentialsAvailable()) {
      StartUp();
    }
  } else if (AreCredentialsAvailable()) {
    // If we have credentials and sync setup finished, autostart the backend.
    // Note that if we haven't finished setting up sync, backend bring up will
    // be done by the wizard.
    StartUp();
  }
}

void ProfileSyncService::RegisterAuthNotifications() {
  registrar_.Add(this,
                 NotificationType::TOKEN_AVAILABLE,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 NotificationType::TOKEN_LOADING_FINISHED,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 NotificationType::GOOGLE_SIGNIN_SUCCESSFUL,
                 Source<Profile>(profile_));
  registrar_.Add(this,
                 NotificationType::GOOGLE_SIGNIN_FAILED,
                 Source<Profile>(profile_));
}

void ProfileSyncService::RegisterDataTypeController(
    DataTypeController* data_type_controller) {
  DCHECK_EQ(data_type_controllers_.count(data_type_controller->type()), 0U);
  data_type_controllers_[data_type_controller->type()] =
      data_type_controller;
}

browser_sync::SessionModelAssociator*
    ProfileSyncService::GetSessionModelAssociator() {
  if (data_type_controllers_.find(syncable::SESSIONS) ==
      data_type_controllers_.end() ||
      data_type_controllers_.find(syncable::SESSIONS)->second->state() !=
      DataTypeController::RUNNING) {
    return NULL;
  }
  return static_cast<browser_sync::SessionDataTypeController*>(
      data_type_controllers_.find(
      syncable::SESSIONS)->second.get())->GetModelAssociator();
}

void ProfileSyncService::ResetClearServerDataState() {
  clear_server_data_state_ = CLEAR_NOT_STARTED;
}

ProfileSyncService::ClearServerDataState
    ProfileSyncService::GetClearServerDataState() {
  return clear_server_data_state_;
}

void ProfileSyncService::GetDataTypeControllerStates(
  browser_sync::DataTypeController::StateMap* state_map) const {
    for (browser_sync::DataTypeController::TypeMap::const_iterator iter =
         data_type_controllers_.begin(); iter != data_type_controllers_.end();
         ++iter)
      (*state_map)[iter->first] = iter->second.get()->state();
}

void ProfileSyncService::InitSettings() {
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();

  // Override the sync server URL from the command-line, if sync server
  // command-line argument exists.
  if (command_line.HasSwitch(switches::kSyncServiceURL)) {
    std::string value(command_line.GetSwitchValueASCII(
        switches::kSyncServiceURL));
    if (!value.empty()) {
      GURL custom_sync_url(value);
      if (custom_sync_url.is_valid()) {
        sync_service_url_ = custom_sync_url;
      } else {
        LOG(WARNING) << "The following sync URL specified at the command-line "
                     << "is invalid: " << value;
      }
    }
  }
}

void ProfileSyncService::RegisterPreferences() {
  PrefService* pref_service = profile_->GetPrefs();
  if (pref_service->FindPreference(prefs::kSyncLastSyncedTime))
    return;
  pref_service->RegisterInt64Pref(prefs::kSyncLastSyncedTime, 0);
  pref_service->RegisterBooleanPref(prefs::kSyncHasSetupCompleted, false);
  pref_service->RegisterBooleanPref(prefs::kSyncSuppressStart, false);

  // If you've never synced before, or if you're using Chrome OS, all datatypes
  // are on by default.
  // TODO(nick): Perhaps a better model would be to always default to false,
  // and explicitly call SetDataTypes() when the user shows the wizard.
#if defined(OS_CHROMEOS)
  bool enable_by_default = true;
#else
  bool enable_by_default =
      !pref_service->HasPrefPath(prefs::kSyncHasSetupCompleted);
#endif

  pref_service->RegisterBooleanPref(prefs::kSyncBookmarks, true);
  pref_service->RegisterBooleanPref(prefs::kSyncPasswords, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncPreferences, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncAutofill, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncThemes, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncTypedUrls, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncExtensions, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncApps, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncSessions, enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kKeepEverythingSynced,
      enable_by_default);
  pref_service->RegisterBooleanPref(prefs::kSyncManaged, false);
  pref_service->RegisterStringPref(prefs::kEncryptionBootstrapToken, "");

  pref_service->RegisterBooleanPref(prefs::kSyncAutofillProfile,
      enable_by_default);
}

void ProfileSyncService::ClearPreferences() {
  PrefService* pref_service = profile_->GetPrefs();
  pref_service->ClearPref(prefs::kSyncLastSyncedTime);
  pref_service->ClearPref(prefs::kSyncHasSetupCompleted);
  pref_service->ClearPref(prefs::kEncryptionBootstrapToken);

  // TODO(nick): The current behavior does not clear e.g. prefs::kSyncBookmarks.
  // Is that really what we want?
  pref_service->ScheduleSavePersistentPrefs();
}

SyncCredentials ProfileSyncService::GetCredentials() {
  SyncCredentials credentials;
  credentials.email = !cros_user_.empty() ? cros_user_ : signin_->GetUsername();
  DCHECK(!credentials.email.empty());
  TokenService* service = profile_->GetTokenService();
  credentials.sync_token = service->GetTokenForService(
      GaiaConstants::kSyncService);
  return credentials;
}

void ProfileSyncService::InitializeBackend(bool delete_sync_data_folder) {
  if (!backend_.get()) {
    NOTREACHED();
    return;
  }

  syncable::ModelTypeSet types;
  // If sync setup hasn't finished, we don't want to initialize routing info
  // for any data types so that we don't download updates for types that the
  // user chooses not to sync on the first DownloadUpdatesCommand.
  if (HasSyncSetupCompleted()) {
    GetPreferredDataTypes(&types);
  }

  SyncCredentials credentials = GetCredentials();

  backend_->Initialize(this,
                       sync_service_url_,
                       types,
                       profile_->GetRequestContext(),
                       credentials,
                       delete_sync_data_folder);
}

void ProfileSyncService::CreateBackend() {
  backend_.reset(new SyncBackendHost(profile_));
}

bool ProfileSyncService::IsEncryptedDatatypeEnabled() const {
  return !encrypted_types_.empty();
}

void ProfileSyncService::StartUp() {
  // Don't start up multiple times.
  if (backend_.get()) {
    VLOG(1) << "Skipping bringing up backend host.";
    return;
  }

  DCHECK(AreCredentialsAvailable());

  last_synced_time_ = base::Time::FromInternalValue(
      profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime));

  CreateBackend();

  // Initialize the backend.  Every time we start up a new SyncBackendHost,
  // we'll want to start from a fresh SyncDB, so delete any old one that might
  // be there.
  InitializeBackend(!HasSyncSetupCompleted());
}

void ProfileSyncService::Shutdown(bool sync_disabled) {
  // Stop all data type controllers, if needed.
  if (data_type_manager_.get()) {
    if (data_type_manager_->state() != DataTypeManager::STOPPED) {
      data_type_manager_->Stop();
    }

    registrar_.Remove(this,
                      NotificationType::SYNC_CONFIGURE_START,
                      Source<DataTypeManager>(data_type_manager_.get()));
    registrar_.Remove(this,
                      NotificationType::SYNC_CONFIGURE_DONE,
                      Source<DataTypeManager>(data_type_manager_.get()));
    data_type_manager_.reset();
  }

  js_event_handlers_.RemoveBackend();

  // Move aside the backend so nobody else tries to use it while we are
  // shutting it down.
  scoped_ptr<SyncBackendHost> doomed_backend(backend_.release());
  if (doomed_backend.get()) {
    doomed_backend->Shutdown(sync_disabled);

    doomed_backend.reset();
  }

  // Clear various flags.
  is_auth_in_progress_ = false;
  backend_initialized_ = false;
  observed_passphrase_required_ = false;
  last_attempted_user_email_.clear();
  last_auth_error_ = GoogleServiceAuthError::None();
}

void ProfileSyncService::ClearServerData() {
  clear_server_data_state_ = CLEAR_CLEARING;
  clear_server_data_timer_.Start(
      base::TimeDelta::FromSeconds(kSyncClearDataTimeoutInSeconds), this,
      &ProfileSyncService::OnClearServerDataTimeout);
  backend_->RequestClearServerData();
}

void ProfileSyncService::DisableForUser() {
  // Clear prefs (including SyncSetupHasCompleted) before shutting down so
  // PSS clients don't think we're set up while we're shutting down.
  ClearPreferences();
  Shutdown(true);

  if (signin_.get()) {
    signin_->SignOut();
  }

  NotifyObservers();
}

bool ProfileSyncService::HasSyncSetupCompleted() const {
  return profile_->GetPrefs()->GetBoolean(prefs::kSyncHasSetupCompleted);
}

void ProfileSyncService::SetSyncSetupCompleted() {
  PrefService* prefs = profile()->GetPrefs();
  prefs->SetBoolean(prefs::kSyncHasSetupCompleted, true);
  prefs->SetBoolean(prefs::kSyncSuppressStart, false);

  prefs->ScheduleSavePersistentPrefs();
}

void ProfileSyncService::UpdateLastSyncedTime() {
  last_synced_time_ = base::Time::Now();
  profile_->GetPrefs()->SetInt64(prefs::kSyncLastSyncedTime,
      last_synced_time_.ToInternalValue());
  profile_->GetPrefs()->ScheduleSavePersistentPrefs();
}

void ProfileSyncService::NotifyObservers() {
  FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
  // TODO(akalin): Make an Observer subclass that listens and does the
  // event routing.
  js_event_handlers_.RouteJsEvent(
      "onSyncServiceStateChanged", browser_sync::JsArgList(), NULL);
}

// static
const char* ProfileSyncService::GetPrefNameForDataType(
    syncable::ModelType data_type) {
  switch (data_type) {
    case syncable::BOOKMARKS:
      return prefs::kSyncBookmarks;
    case syncable::PASSWORDS:
      return prefs::kSyncPasswords;
    case syncable::PREFERENCES:
      return prefs::kSyncPreferences;
    case syncable::AUTOFILL:
      return prefs::kSyncAutofill;
    case syncable::AUTOFILL_PROFILE:
      return prefs::kSyncAutofillProfile;
    case syncable::THEMES:
      return prefs::kSyncThemes;
    case syncable::TYPED_URLS:
      return prefs::kSyncTypedUrls;
    case syncable::EXTENSIONS:
      return prefs::kSyncExtensions;
    case syncable::APPS:
      return prefs::kSyncApps;
    case syncable::SESSIONS:
      return prefs::kSyncSessions;
    default:
      break;
  }
  NOTREACHED();
  return NULL;
}

// An invariant has been violated.  Transition to an error state where we try
// to do as little work as possible, to avoid further corruption or crashes.
void ProfileSyncService::OnUnrecoverableError(
    const tracked_objects::Location& from_here,
    const std::string& message) {
  unrecoverable_error_detected_ = true;
  unrecoverable_error_message_ = message;
  unrecoverable_error_location_.reset(
      new tracked_objects::Location(from_here.function_name(),
                                    from_here.file_name(),
                                    from_here.line_number()));

  // Tell the wizard so it can inform the user only if it is already open.
  wizard_.Step(SyncSetupWizard::FATAL_ERROR);

  NotifyObservers();
  LOG(ERROR) << "Unrecoverable error detected -- ProfileSyncService unusable."
      << message;
  std::string location;
  from_here.Write(true, true, &location);
  LOG(ERROR) << location;

  // Shut all data types down.
  MessageLoop::current()->PostTask(FROM_HERE,
        scoped_runnable_method_factory_.NewRunnableMethod(
        &ProfileSyncService::Shutdown, true));
}

void ProfileSyncService::OnBackendInitialized() {
  backend_initialized_ = true;

  js_event_handlers_.SetBackend(backend_->GetJsBackend());

  // The very first time the backend initializes is effectively the first time
  // we can say we successfully "synced".  last_synced_time_ will only be null
  // in this case, because the pref wasn't restored on StartUp.
  if (last_synced_time_.is_null()) {
    UpdateLastSyncedTime();
  }
  NotifyObservers();

  if (!cros_user_.empty()) {
    if (profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)) {
      ShowConfigure(NULL, true);
    } else {
      SetSyncSetupCompleted();
    }
  }

  if (HasSyncSetupCompleted()) {
    ConfigureDataTypeManager();
  }
}

void ProfileSyncService::OnSyncCycleCompleted() {
  UpdateLastSyncedTime();
  VLOG(2) << "Notifying observers sync cycle completed";
  NotifyObservers();
}

void ProfileSyncService::UpdateAuthErrorState(
    const GoogleServiceAuthError& error) {
  last_auth_error_ = error;
  // Protect against the in-your-face dialogs that pop out of nowhere.
  // Require the user to click somewhere to run the setup wizard in the case
  // of a steady-state auth failure.
  if (WizardIsVisible()) {
    wizard_.Step(AuthError::NONE == last_auth_error_.state() ?
        SyncSetupWizard::GAIA_SUCCESS : SyncSetupWizard::GAIA_LOGIN);
  } else {
    auth_error_time_ = base::TimeTicks::Now();
  }

  if (!auth_start_time_.is_null()) {
    UMA_HISTOGRAM_TIMES("Sync.AuthorizationTimeInNetwork",
                    base::TimeTicks::Now() - auth_start_time_);
    auth_start_time_ = base::TimeTicks();
  }

  is_auth_in_progress_ = false;
  // Fan the notification out to interested UI-thread components.
  NotifyObservers();
}

void ProfileSyncService::OnAuthError() {
  UpdateAuthErrorState(backend_->GetAuthError());
}

void ProfileSyncService::OnStopSyncingPermanently() {
  if (SetupInProgress()) {
    wizard_.Step(SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR);
    expect_sync_configuration_aborted_ = true;
  }
  profile_->GetPrefs()->SetBoolean(prefs::kSyncSuppressStart, true);
  DisableForUser();
}

void ProfileSyncService::OnClearServerDataTimeout() {
  if (clear_server_data_state_ != CLEAR_SUCCEEDED &&
      clear_server_data_state_ != CLEAR_FAILED) {
    clear_server_data_state_ = CLEAR_FAILED;
    NotifyObservers();
  }
}

void ProfileSyncService::OnClearServerDataFailed() {
  clear_server_data_timer_.Stop();

  // Only once clear has succeeded there is no longer a need to transition to
  // a failed state as sync is disabled locally.  Also, no need to fire off
  // the observers if the state didn't change (i.e. it was FAILED before).
  if (clear_server_data_state_ != CLEAR_SUCCEEDED &&
      clear_server_data_state_ != CLEAR_FAILED) {
    clear_server_data_state_ = CLEAR_FAILED;
    NotifyObservers();
  }
}

void ProfileSyncService::OnClearServerDataSucceeded() {
  clear_server_data_timer_.Stop();

  // Even if the timout fired, we still transition to the succeeded state as
  // we want UI to update itself and no longer allow the user to press "clear"
  if (clear_server_data_state_ != CLEAR_SUCCEEDED) {
    clear_server_data_state_ = CLEAR_SUCCEEDED;
    NotifyObservers();
  }
}

void ProfileSyncService::OnPassphraseRequired(bool for_decryption) {
  DCHECK(backend_.get());
  DCHECK(backend_->IsNigoriEnabled());

  // TODO(lipalani) : add this check to other locations as well.
  if (unrecoverable_error_detected_) {
    // When unrecoverable error is detected we post a task to shutdown the
    // backend. The task might not have executed yet.
    return;
  }
  observed_passphrase_required_ = true;
  passphrase_required_for_decryption_ = for_decryption;

  // First try supplying gaia password as the passphrase.
  if (!gaia_password_.empty()) {
    SetPassphrase(gaia_password_, false, true);
    gaia_password_ = std::string();
    return;
  }

  // If the above failed then try the custom passphrase the user might have
  // entered in setup.
  if (!cached_passphrase_.value.empty()) {
    SetPassphrase(cached_passphrase_.value,
                  cached_passphrase_.is_explicit,
                  cached_passphrase_.is_creation);
    cached_passphrase_ = CachedPassphrase();
    return;
  }

  // We will skip the passphrase prompt and suppress the warning
  // if the passphrase is needed for decryption but the user is
  // not syncing an encrypted data type on this machine.
  // Otherwise we prompt.
  if (!IsEncryptedDatatypeEnabled() && for_decryption) {
    OnPassphraseAccepted();
    return;
  }

  if (WizardIsVisible() && for_decryption) {
    wizard_.Step(SyncSetupWizard::ENTER_PASSPHRASE);
  }

  NotifyObservers();
}

void ProfileSyncService::OnPassphraseAccepted() {
  // Make sure the data types that depend on the passphrase are started at
  // this time.
  syncable::ModelTypeSet types;
  GetPreferredDataTypes(&types);
  // Reset "passphrase_required" flag before configuring the DataTypeManager
  // since we know we no longer require the passphrase.
  observed_passphrase_required_ = false;
  if (data_type_manager_.get())
    data_type_manager_->Configure(types);

  NotifyObservers();

  wizard_.Step(SyncSetupWizard::DONE);
}

void ProfileSyncService::OnEncryptionComplete(
    const syncable::ModelTypeSet& encrypted_types) {
  if (encrypted_types_ != encrypted_types) {
    encrypted_types_ = encrypted_types;
    NotifyObservers();
  }
}

void ProfileSyncService::OnMigrationNeededForTypes(
    const syncable::ModelTypeSet& types) {
  DCHECK(backend_initialized_);
  DCHECK(data_type_manager_.get());

  // 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 ProfileSyncService::ShowLoginDialog(gfx::NativeWindow parent_window) {
  if (WizardIsVisible()) {
    wizard_.Focus();
    // Force the wizard to step to the login screen (which will only actually
    // happen if the transition is valid).
    wizard_.Step(SyncSetupWizard::GAIA_LOGIN);
    return;
  }

  if (!auth_error_time_.is_null()) {
    UMA_HISTOGRAM_LONG_TIMES("Sync.ReauthorizationTime",
                             base::TimeTicks::Now() - auth_error_time_);
    auth_error_time_ = base::TimeTicks();  // Reset auth_error_time_ to null.
  }

  wizard_.Step(SyncSetupWizard::GAIA_LOGIN);

  NotifyObservers();
}

void ProfileSyncService::ShowErrorUI(gfx::NativeWindow parent_window) {
  if (observed_passphrase_required()) {
    if (IsUsingSecondaryPassphrase())
      PromptForExistingPassphrase(parent_window);
    else
      SigninForPassphraseMigration(parent_window);
    return;
  }
  const GoogleServiceAuthError& error = GetAuthError();
  if (error.state() == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS ||
      error.state() == GoogleServiceAuthError::CAPTCHA_REQUIRED ||
      error.state() == GoogleServiceAuthError::ACCOUNT_DELETED ||
      error.state() == GoogleServiceAuthError::ACCOUNT_DISABLED ||
      error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
    ShowLoginDialog(parent_window);
  }
}


void ProfileSyncService::ShowConfigure(
    gfx::NativeWindow parent_window, bool sync_everything) {
  if (WizardIsVisible()) {
    wizard_.Focus();
    return;
  }

  if (sync_everything)
    wizard_.Step(SyncSetupWizard::SYNC_EVERYTHING);
  else
    wizard_.Step(SyncSetupWizard::CONFIGURE);
}

void ProfileSyncService::PromptForExistingPassphrase(
    gfx::NativeWindow parent_window) {
  if (WizardIsVisible()) {
    wizard_.Focus();
    return;
  }

  wizard_.Step(SyncSetupWizard::ENTER_PASSPHRASE);
}

void ProfileSyncService::SigninForPassphraseMigration(
    gfx::NativeWindow parent_window) {
  passphrase_migration_in_progress_ = true;
  ShowLoginDialog(parent_window);
}

SyncBackendHost::StatusSummary ProfileSyncService::QuerySyncStatusSummary() {
  if (backend_.get() && backend_initialized_)
    return backend_->GetStatusSummary();
  else
    return SyncBackendHost::Status::OFFLINE_UNUSABLE;
}

SyncBackendHost::Status ProfileSyncService::QueryDetailedSyncStatus() {
  if (backend_.get() && backend_initialized_) {
    return backend_->GetDetailedStatus();
  } else {
    SyncBackendHost::Status status =
        { SyncBackendHost::Status::OFFLINE_UNUSABLE };
    return status;
  }
}

bool ProfileSyncService::SetupInProgress() const {
  return !HasSyncSetupCompleted() && WizardIsVisible();
}

std::string ProfileSyncService::BuildSyncStatusSummaryText(
    const sync_api::SyncManager::Status::Summary& summary) {
  const char* strings[] = {"INVALID", "OFFLINE", "OFFLINE_UNSYNCED", "SYNCING",
      "READY", "CONFLICT", "OFFLINE_UNUSABLE"};
  COMPILE_ASSERT(arraysize(strings) ==
                 sync_api::SyncManager::Status::SUMMARY_STATUS_COUNT,
                 enum_indexed_array);
  if (summary < 0 ||
      summary >= sync_api::SyncManager::Status::SUMMARY_STATUS_COUNT) {
    LOG(DFATAL) << "Illegal Summary Value: " << summary;
    return "UNKNOWN";
  }
  return strings[summary];
}

bool ProfileSyncService::unrecoverable_error_detected() const {
  return unrecoverable_error_detected_;
}

string16 ProfileSyncService::GetLastSyncedTimeString() const {
  if (last_synced_time_.is_null())
    return l10n_util::GetStringUTF16(IDS_SYNC_TIME_NEVER);

  base::TimeDelta last_synced = base::Time::Now() - last_synced_time_;

  if (last_synced < base::TimeDelta::FromMinutes(1))
    return l10n_util::GetStringUTF16(IDS_SYNC_TIME_JUST_NOW);

  return TimeFormat::TimeElapsed(last_synced);
}

string16 ProfileSyncService::GetAuthenticatedUsername() const {
  if (backend_.get() && backend_initialized_)
    return backend_->GetAuthenticatedUsername();
  else
    return string16();
}

void ProfileSyncService::OnUserSubmittedAuth(
    const std::string& username, const std::string& password,
    const std::string& captcha, const std::string& access_code) {
  last_attempted_user_email_ = username;
  is_auth_in_progress_ = true;
  NotifyObservers();

  auth_start_time_ = base::TimeTicks::Now();

  if (!signin_.get()) {
    // In ChromeOS we sign in during login, so we do not instantiate signin_.
    // If this function gets called, we need to re-authenticate (e.g. for
    // two factor signin), so instantiate signin_ here.
    signin_.reset(new SigninManager());
    signin_->Initialize(profile_);
  }

  if (!access_code.empty()) {
    signin_->ProvideSecondFactorAccessCode(access_code);
    return;
  }

  if (!signin_->GetUsername().empty()) {
    signin_->SignOut();
  }

  // The user has submitted credentials, which indicates they don't
  // want to suppress start up anymore.
  PrefService* prefs = profile_->GetPrefs();
  prefs->SetBoolean(prefs::kSyncSuppressStart, false);
  prefs->ScheduleSavePersistentPrefs();

  signin_->StartSignIn(username,
                       password,
                       last_auth_error_.captcha().token,
                       captcha);
}

void ProfileSyncService::OnUserChoseDatatypes(bool sync_everything,
    const syncable::ModelTypeSet& chosen_types) {
  if (!backend_.get()) {
    NOTREACHED();
    return;
  }

  profile_->GetPrefs()->SetBoolean(prefs::kKeepEverythingSynced,
      sync_everything);

  ChangePreferredDataTypes(chosen_types);
  profile_->GetPrefs()->ScheduleSavePersistentPrefs();
}

void ProfileSyncService::OnUserCancelledDialog() {
  if (!HasSyncSetupCompleted()) {
    // A sync dialog was aborted before authentication.
    // Rollback.
    expect_sync_configuration_aborted_ = true;
    DisableForUser();
  }

  // Though an auth could still be in progress, once the dialog is closed we
  // don't want the UI to stay stuck in the "waiting for authentication" state
  // as that could take forever.  We set this to false so the buttons to re-
  // login will appear until either a) the original request finishes and
  // succeeds, calling OnAuthError(NONE), or b) the user clicks the button,
  // and tries to re-authenticate. (b) is a little awkward as this second
  // request will get queued behind the first and could wind up "undoing" the
  // good if invalid creds were provided, but it's an edge case and the user
  // can of course get themselves out of it.
  is_auth_in_progress_ = false;
  NotifyObservers();
}

void ProfileSyncService::ChangePreferredDataTypes(
    const syncable::ModelTypeSet& preferred_types) {

  // Filter out any datatypes which aren't registered, or for which
  // the preference can't be set.
  syncable::ModelTypeSet registered_types;
  GetRegisteredDataTypes(&registered_types);
  for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) {
    syncable::ModelType model_type = syncable::ModelTypeFromInt(i);
    if (!registered_types.count(model_type))
      continue;
    const char* pref_name = GetPrefNameForDataType(model_type);
    if (!pref_name)
      continue;
    profile_->GetPrefs()->SetBoolean(pref_name,
        preferred_types.count(model_type) != 0);
    if (syncable::AUTOFILL == model_type) {
      profile_->GetPrefs()->SetBoolean(prefs::kSyncAutofillProfile,
          preferred_types.count(model_type) != 0);
    }
  }

  // If we haven't initialized yet, don't configure the DTM as it could cause
  // association to start before a Directory has even been created.
  if (backend_initialized_)
    ConfigureDataTypeManager();
}

void ProfileSyncService::GetPreferredDataTypes(
    syncable::ModelTypeSet* preferred_types) const {
  preferred_types->clear();
  if (profile_->GetPrefs()->GetBoolean(prefs::kKeepEverythingSynced)) {
    GetRegisteredDataTypes(preferred_types);
  } else {
    // Filter out any datatypes which aren't registered, or for which
    // the preference can't be read.
    syncable::ModelTypeSet registered_types;
    GetRegisteredDataTypes(&registered_types);
    for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) {
      syncable::ModelType model_type = syncable::ModelTypeFromInt(i);
      if (!registered_types.count(model_type))
        continue;
      if (model_type == syncable::AUTOFILL_PROFILE)
        continue;
      const char* pref_name = GetPrefNameForDataType(model_type);
      if (!pref_name)
        continue;

      // We are trying to group autofill_profile tag with the same
      // enabled/disabled state as autofill. Because the UI only shows autofill.
      if (profile_->GetPrefs()->GetBoolean(pref_name)) {
        preferred_types->insert(model_type);
        if (model_type == syncable::AUTOFILL) {
          if (!registered_types.count(syncable::AUTOFILL_PROFILE))
            continue;
          preferred_types->insert(syncable::AUTOFILL_PROFILE);
        }
      }
    }
  }
}

void ProfileSyncService::GetRegisteredDataTypes(
    syncable::ModelTypeSet* registered_types) const {
  registered_types->clear();
  // The data_type_controllers_ are determined by command-line flags; that's
  // effectively what controls the values returned here.
  for (DataTypeController::TypeMap::const_iterator it =
       data_type_controllers_.begin();
       it != data_type_controllers_.end(); ++it) {
    registered_types->insert((*it).first);
  }
}

bool ProfileSyncService::IsUsingSecondaryPassphrase() const {
  return backend_.get() && (backend_->IsUsingExplicitPassphrase() ||
      (tried_implicit_gaia_remove_when_bug_62103_fixed_ &&
       observed_passphrase_required_));
}

bool ProfileSyncService::IsCryptographerReady(
    const sync_api::BaseTransaction* trans) const {
  return backend_.get() && backend_->IsCryptographerReady(trans);
}

SyncBackendHost* ProfileSyncService::GetBackendForTest() {
  // We don't check |backend_initialized_|; we assume the test class
  // knows what it's doing.
  return backend_.get();
}

void ProfileSyncService::ConfigureDataTypeManager() {
  if (!data_type_manager_.get()) {
    data_type_manager_.reset(
        factory_->CreateDataTypeManager(backend_.get(),
                                        data_type_controllers_));
    registrar_.Add(this,
                   NotificationType::SYNC_CONFIGURE_START,
                   Source<DataTypeManager>(data_type_manager_.get()));
    registrar_.Add(this,
                   NotificationType::SYNC_CONFIGURE_DONE,
                   Source<DataTypeManager>(data_type_manager_.get()));

    // We create the migrator at the same time.
    migrator_.reset(
        new browser_sync::BackendMigrator(this, data_type_manager_.get()));
  }

  syncable::ModelTypeSet types;
  GetPreferredDataTypes(&types);
  // We set this special case here since it's the only datatype whose encryption
  // status we already know. All others are set after the initial sync
  // completes (for now).
  // TODO(zea): Implement a better way that uses preferences for which types
  // need encryption.
  encrypted_types_.clear();
  if (types.count(syncable::PASSWORDS) > 0)
    encrypted_types_.insert(syncable::PASSWORDS);
  if (observed_passphrase_required_ && passphrase_required_for_decryption_) {
    if (IsEncryptedDatatypeEnabled()) {
      // We need a passphrase still. Prompt the user for a passphrase, and
      // DataTypeManager::Configure() will get called once the passphrase is
      // accepted.
      OnPassphraseRequired(true);
      return;
    } else {
      // We've been informed that a passphrase is required for decryption, but
      // now there are no encrypted data types enabled, so clear the flag
      // (NotifyObservers() will be called when configuration completes).
      observed_passphrase_required_ = false;
    }
  }
  data_type_manager_->Configure(types);
}

sync_api::UserShare* ProfileSyncService::GetUserShare() const {
  if (backend_.get() && backend_initialized_) {
    return backend_->GetUserShare();
  }
  NOTREACHED();
  return NULL;
}

const browser_sync::sessions::SyncSessionSnapshot*
    ProfileSyncService::GetLastSessionSnapshot() const {
  if (backend_.get() && backend_initialized_) {
    return backend_->GetLastSessionSnapshot();
  }
  NOTREACHED();
  return NULL;
}

bool ProfileSyncService::HasUnsyncedItems() const {
  if (backend_.get() && backend_initialized_) {
    return backend_->HasUnsyncedItems();
  }
  NOTREACHED();
  return false;
}

void ProfileSyncService::GetModelSafeRoutingInfo(
    browser_sync::ModelSafeRoutingInfo* out) {
  if (backend_.get() && backend_initialized_) {
    backend_->GetModelSafeRoutingInfo(out);
  } else {
    NOTREACHED();
  }
}

syncable::AutofillMigrationState
    ProfileSyncService::GetAutofillMigrationState() {
  if (backend_.get() && backend_initialized_) {
    return backend_->GetAutofillMigrationState();
  }
  NOTREACHED();
  return syncable::NOT_DETERMINED;
}

void ProfileSyncService::SetAutofillMigrationState(
    syncable::AutofillMigrationState state) {
  if (backend_.get() && backend_initialized_) {
    backend_->SetAutofillMigrationState(state);
  } else {
    NOTREACHED();
  }
}

syncable::AutofillMigrationDebugInfo
    ProfileSyncService::GetAutofillMigrationDebugInfo() {
  if (backend_.get() && backend_initialized_) {
    return backend_->GetAutofillMigrationDebugInfo();
  }
  NOTREACHED();
  syncable::AutofillMigrationDebugInfo debug_info = { 0 };
  return debug_info;
}

void ProfileSyncService::SetAutofillMigrationDebugInfo(
    syncable::AutofillMigrationDebugInfo::PropertyToSet property_to_set,
    const syncable::AutofillMigrationDebugInfo& info) {
  if (backend_.get() && backend_initialized_) {
    backend_->SetAutofillMigrationDebugInfo(property_to_set, info);
  } else {
    NOTREACHED();
  }
}

void ProfileSyncService::ActivateDataType(
    DataTypeController* data_type_controller,
    ChangeProcessor* change_processor) {
  if (!backend_.get()) {
    NOTREACHED();
    return;
  }
  DCHECK(backend_initialized_);
  change_processor->Start(profile(), backend_->GetUserShare());
  backend_->ActivateDataType(data_type_controller, change_processor);
}

void ProfileSyncService::DeactivateDataType(
    DataTypeController* data_type_controller,
    ChangeProcessor* change_processor) {
  change_processor->Stop();
  if (backend_.get())
    backend_->DeactivateDataType(data_type_controller, change_processor);
}

void ProfileSyncService::SetPassphrase(const std::string& passphrase,
                                       bool is_explicit,
                                       bool is_creation) {
  if (ShouldPushChanges() || observed_passphrase_required_) {
    backend_->SetPassphrase(passphrase, is_explicit);
  } else {
    if (is_explicit) {
      cached_passphrase_.value = passphrase;
      cached_passphrase_.is_explicit = is_explicit;
      cached_passphrase_.is_creation = is_creation;
    } else {
      gaia_password_ = passphrase;
    }
  }
}

void ProfileSyncService::EncryptDataTypes(
    const syncable::ModelTypeSet& encrypted_types) {
  backend_->EncryptDataTypes(encrypted_types);
}

void ProfileSyncService::GetEncryptedDataTypes(
    syncable::ModelTypeSet* encrypted_types) const {
  *encrypted_types = encrypted_types_;
}

void ProfileSyncService::Observe(NotificationType type,
                                 const NotificationSource& source,
                                 const NotificationDetails& details) {
  switch (type.value) {
    case NotificationType::SYNC_CONFIGURE_START: {
      NotifyObservers();
      // TODO(sync): Maybe toast?
      break;
    }
    case NotificationType::SYNC_CONFIGURE_DONE: {
      DataTypeManager::ConfigureResultWithErrorLocation* result_with_location =
          Details<DataTypeManager::ConfigureResultWithErrorLocation>(
              details).ptr();

      DataTypeManager::ConfigureResult result = result_with_location->result;
      if (result == DataTypeManager::ABORTED &&
          expect_sync_configuration_aborted_) {
        expect_sync_configuration_aborted_ = false;
        return;
      }
      // Clear out the gaia password if it is already there.
      gaia_password_ = std::string();
      if (result != DataTypeManager::OK) {
        std::string message = StringPrintf("Sync Configuration failed with %d",
                                            result);
        OnUnrecoverableError(*(result_with_location->location), message);
        cached_passphrase_ = CachedPassphrase();
        return;
      }

      // If the user had entered a custom passphrase use it now.
      if (!cached_passphrase_.value.empty()) {
        // Don't hold on to the passphrase in raw form longer than needed.
        SetPassphrase(cached_passphrase_.value,
                      cached_passphrase_.is_explicit,
                      cached_passphrase_.is_creation);
        cached_passphrase_ = CachedPassphrase();
      }

      // We should never get in a state where we have no encrypted datatypes
      // enabled, and yet we still think we require a passphrase.
      DCHECK(!(observed_passphrase_required_ &&
               passphrase_required_for_decryption_ &&
               !IsEncryptedDatatypeEnabled()));

      // TODO(sync): Less wizard, more toast.
      wizard_.Step(SyncSetupWizard::DONE);
      NotifyObservers();

      // In the old world, this would be a no-op.  With new syncer thread,
      // this is the point where it is safe to switch from config-mode to
      // normal operation.
      backend_->StartSyncingWithServer();
      break;
    }
    case NotificationType::SYNC_DATA_TYPES_UPDATED: {
      if (!HasSyncSetupCompleted()) break;

      syncable::ModelTypeSet types;
      GetPreferredDataTypes(&types);
      OnUserChoseDatatypes(false, types);
      break;
    }
    case NotificationType::PREF_CHANGED: {
      std::string* pref_name = Details<std::string>(details).ptr();
      if (*pref_name == prefs::kSyncManaged) {
        NotifyObservers();
        if (*pref_sync_managed_) {
          DisableForUser();
        } else if (HasSyncSetupCompleted() && AreCredentialsAvailable()) {
          StartUp();
        }
      }
      break;
    }
    case NotificationType::GOOGLE_SIGNIN_SUCCESSFUL: {
      const GoogleServiceSigninSuccessDetails* successful =
          (Details<const GoogleServiceSigninSuccessDetails>(details).ptr());
      // We pass 'false' to SetPassphrase to denote that this is an implicit
      // request and shouldn't override an explicit one.  Thus, we either
      // update the implicit passphrase (idempotent if the passphrase didn't
      // actually change), or the user has an explicit passphrase set so this
      // becomes a no-op.
      tried_implicit_gaia_remove_when_bug_62103_fixed_ = true;
      SetPassphrase(successful->password, false, true);

      // If this signin was to initiate a passphrase migration (on the
      // first computer, thus not for decryption), continue the migration.
      if (passphrase_migration_in_progress_ &&
          !passphrase_required_for_decryption_) {
        wizard_.Step(SyncSetupWizard::PASSPHRASE_MIGRATION);
        passphrase_migration_in_progress_ = false;
      }

      break;
    }
    case NotificationType::GOOGLE_SIGNIN_FAILED: {
      GoogleServiceAuthError error =
          *(Details<const GoogleServiceAuthError>(details).ptr());
      UpdateAuthErrorState(error);
      break;
    }
    case NotificationType::TOKEN_AVAILABLE: {
      if (AreCredentialsAvailable()) {
        if (backend_initialized_) {
          backend_->UpdateCredentials(GetCredentials());
        }

        if (!profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart))
          StartUp();
      }
      break;
    }
    case NotificationType::TOKEN_LOADING_FINISHED: {
      // If not in Chrome OS, and we have a username without tokens,
      // the user will need to signin again, so sign out.
      if (cros_user_.empty() &&
          !signin_->GetUsername().empty() &&
          !AreCredentialsAvailable()) {
        DisableForUser();
      }
      break;
    }
    default: {
      NOTREACHED();
    }
  }
}

void ProfileSyncService::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void ProfileSyncService::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

bool ProfileSyncService::HasObserver(Observer* observer) const {
  return observers_.HasObserver(observer);
}

browser_sync::JsFrontend* ProfileSyncService::GetJsFrontend() {
  return &js_event_handlers_;
}

void ProfileSyncService::SyncEvent(SyncEventCodes code) {
  UMA_HISTOGRAM_ENUMERATION("Sync.EventCodes", code, MAX_SYNC_EVENT_CODE);
}

// static
bool ProfileSyncService::IsSyncEnabled() {
  // We have switches::kEnableSync just in case we need to change back to
  // sync-disabled-by-default on a platform.
  return !CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableSync);
}

bool ProfileSyncService::IsManaged() {
  // Some tests use ProfileSyncServiceMock which doesn't have a profile.
  return profile_ && profile_->GetPrefs()->GetBoolean(prefs::kSyncManaged);
}

bool ProfileSyncService::ShouldPushChanges() {
  // True only after all bootstrapping has succeeded: the sync backend
  // is initialized, all enabled data types are consistent with one
  // another, and no unrecoverable error has transpired.
  if (unrecoverable_error_detected_)
    return false;

  if (!data_type_manager_.get())
    return false;

  return data_type_manager_->state() == DataTypeManager::CONFIGURED;
}