// 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(®istered_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(®istered_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;
}