text: // Copyright (c) 2012 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/ui/webui/sync_setup_handler.h"
  
  #include "base/basictypes.h"
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "base/command_line.h"
  #include "base/compiler_specific.h"
  #include "base/json/json_reader.h"
  #include "base/json/json_writer.h"
  #include "base/utf_string_conversions.h"
  #include "base/values.h"
  #include "chrome/app/chrome_command_ids.h"
  #include "chrome/browser/google/google_util.h"
  #include "chrome/browser/lifetime/application_lifetime.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/profiles/profile_info_cache.h"
  #include "chrome/browser/profiles/profile_manager.h"
  #include "chrome/browser/profiles/profile_metrics.h"
  #include "chrome/browser/signin/signin_manager.h"
  #include "chrome/browser/signin/signin_manager_factory.h"
  #include "chrome/browser/sync/profile_sync_service.h"
  #include "chrome/browser/sync/profile_sync_service_factory.h"
  #include "chrome/browser/ui/webui/signin/login_ui_service.h"
  #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
  #include "chrome/browser/ui/webui/sync_promo/sync_promo_ui.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/common/url_constants.h"
  #include "content/public/browser/render_view_host.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/browser/web_contents_delegate.h"
  #include "google_apis/gaia/gaia_constants.h"
  #include "grit/chromium_strings.h"
  #include "grit/generated_resources.h"
  #include "grit/locale_settings.h"
  #include "ui/base/l10n/l10n_util.h"
  
  using content::WebContents;
  using l10n_util::GetStringFUTF16;
  using l10n_util::GetStringUTF16;
  
  namespace {
  
  // A structure which contains all the configuration information for sync.
  struct SyncConfigInfo {
    SyncConfigInfo();
    ~SyncConfigInfo();
  
    bool encrypt_all;
    bool sync_everything;
    syncer::ModelTypeSet data_types;
    std::string passphrase;
    bool passphrase_is_gaia;
  };
  
  SyncConfigInfo::SyncConfigInfo()
      : encrypt_all(false),
        sync_everything(false),
        passphrase_is_gaia(false) {
  }
  
  SyncConfigInfo::~SyncConfigInfo() {}
  
  const char* kDataTypeNames[] = {
    "apps",
    "autofill",
    "bookmarks",
    "extensions",
    "passwords",
    "preferences",
    "sessions",
    "themes",
    "typedUrls"
  };
  
  const syncer::ModelType kDataTypes[] = {
    syncer::APPS,
    syncer::AUTOFILL,
    syncer::BOOKMARKS,
    syncer::EXTENSIONS,
    syncer::PASSWORDS,
    syncer::PREFERENCES,
    syncer::SESSIONS,
    syncer::THEMES,
    syncer::TYPED_URLS
  };
  
  static const size_t kNumDataTypes = arraysize(kDataTypes);
  COMPILE_ASSERT(arraysize(kDataTypeNames) == arraysize(kDataTypes),
                 kDataTypes_does_not_match_kDataTypeNames);
  
  static const char kDefaultSigninDomain[] = "gmail.com";
  
  bool GetAuthData(const std::string& json,
                   std::string* username,
                   std::string* password,
                   std::string* captcha,
                   std::string* otp,
                   std::string* access_code) {
    scoped_ptr<Value> parsed_value(base::JSONReader::Read(json));
    if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
      return false;
  
    DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
    if (!result->GetString("user", username) ||
        !result->GetString("pass", password) ||
        !result->GetString("captcha", captcha) ||
        !result->GetString("otp", otp) ||
        !result->GetString("accessCode", access_code)) {
        return false;
    }
    return true;
  }
  
  bool GetConfiguration(const std::string& json, SyncConfigInfo* config) {
    scoped_ptr<Value> parsed_value(base::JSONReader::Read(json));
    DictionaryValue* result;
    if (!parsed_value.get() || !parsed_value->GetAsDictionary(&result)) {
      DLOG(ERROR) << "GetConfiguration() not passed a Dictionary";
      return false;
    }
  
    if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything)) {
      DLOG(ERROR) << "GetConfiguration() not passed a syncAllDataTypes value";
      return false;
    }
  
    for (size_t i = 0; i < arraysize(kDataTypeNames); ++i) {
      std::string key_name = kDataTypeNames[i] + std::string("Synced");
      bool sync_value;
      if (!result->GetBoolean(key_name, &sync_value)) {
        DLOG(ERROR) << "GetConfiguration() not passed a value for " << key_name;
        return false;
      }
      if (sync_value)
        config->data_types.Put(kDataTypes[i]);
    }
  
    // Encryption settings.
    if (!result->GetBoolean("encryptAllData", &config->encrypt_all)) {
      DLOG(ERROR) << "GetConfiguration() not passed a value for encryptAllData";
      return false;
    }
  
    // Passphrase settings.
    bool have_passphrase;
    if (!result->GetBoolean("usePassphrase", &have_passphrase)) {
      DLOG(ERROR) << "GetConfiguration() not passed a usePassphrase value";
      return false;
    }
  
    if (have_passphrase) {
      if (!result->GetBoolean("isGooglePassphrase",
                              &config->passphrase_is_gaia)) {
        DLOG(ERROR) << "GetConfiguration() not passed isGooglePassphrase value";
        return false;
      }
      if (!result->GetString("passphrase", &config->passphrase)) {
        DLOG(ERROR) << "GetConfiguration() not passed a passphrase value";
        return false;
      }
    }
    return true;
  }
  
  string16 NormalizeUserName(const string16& user) {
    if (user.find_first_of(ASCIIToUTF16("@")) != string16::npos)
      return user;
    return user + ASCIIToUTF16("@") + ASCIIToUTF16(kDefaultSigninDomain);
  }
  
  bool AreUserNamesEqual(const string16& user1, const string16& user2) {
    return NormalizeUserName(user1) == NormalizeUserName(user2);
  }
  
  }  // namespace
  
  SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager)
      : configuring_sync_(false),
        profile_manager_(profile_manager),
        last_signin_error_(GoogleServiceAuthError::NONE),
        retry_on_signin_failure_(true) {
  }
  
  SyncSetupHandler::~SyncSetupHandler() {
    // Just exit if running unit tests (no actual WebUI is attached).
    if (!web_ui())
      return;
  
    // This case is hit when the user performs a back navigation.
    CloseSyncSetup();
  }
  
  void SyncSetupHandler::GetLocalizedValues(DictionaryValue* localized_strings) {
    GetStaticLocalizedValues(localized_strings, web_ui());
  }
  
  void SyncSetupHandler::GetStaticLocalizedValues(
      DictionaryValue* localized_strings,
      content::WebUI* web_ui) {
    DCHECK(localized_strings);
  
    localized_strings->SetString(
        "invalidPasswordHelpURL", chrome::kInvalidPasswordHelpURL);
    localized_strings->SetString(
        "cannotAccessAccountURL", chrome::kCanNotAccessAccountURL);
    string16 product_name(GetStringUTF16(IDS_PRODUCT_NAME));
    localized_strings->SetString(
        "introduction",
        GetStringFUTF16(IDS_SYNC_LOGIN_INTRODUCTION, product_name));
    localized_strings->SetString(
        "chooseDataTypesInstructions",
        GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, product_name));
    localized_strings->SetString(
        "encryptionInstructions",
        GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, product_name));
    localized_strings->SetString(
        "encryptionHelpURL", chrome::kSyncEncryptionHelpURL);
    localized_strings->SetString(
        "passphraseEncryptionMessage",
        GetStringFUTF16(IDS_SYNC_PASSPHRASE_ENCRYPTION_MESSAGE, product_name));
    localized_strings->SetString(
        "passphraseRecover",
        GetStringFUTF16(IDS_SYNC_PASSPHRASE_RECOVER,
                        ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
                            chrome::kSyncGoogleDashboardURL))));
    localized_strings->SetString("stopSyncingExplanation",
        l10n_util::GetStringFUTF16(
            IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL,
            l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
            ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
                chrome::kSyncGoogleDashboardURL))));
    localized_strings->SetString("stopSyncingTitle",
        l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE));
    localized_strings->SetString("stopSyncingConfirm",
          l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL));
  
    bool is_start_page = false;
    if (web_ui) {
      SyncPromoUI::Source source = SyncPromoUI::GetSourceForSyncPromoURL(
          web_ui->GetWebContents()->GetURL());
      is_start_page = source == SyncPromoUI::SOURCE_START_PAGE;
    }
    int title_id = is_start_page ? IDS_SYNC_PROMO_TITLE_SHORT :
                                   IDS_SYNC_PROMO_TITLE_EXISTING_USER;
    string16 short_product_name(GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
    localized_strings->SetString(
        "promoTitle", GetStringFUTF16(title_id, short_product_name));
  
    localized_strings->SetString(
        "syncEverythingHelpURL", chrome::kSyncEverythingLearnMoreURL);
    localized_strings->SetString(
        "syncErrorHelpURL", chrome::kSyncErrorsHelpURL);
  
    std::string create_account_url = google_util::StringAppendGoogleLocaleParam(
        chrome::kSyncCreateNewAccountURL);
    string16 create_account = GetStringUTF16(IDS_SYNC_CREATE_ACCOUNT);
    create_account= UTF8ToUTF16("<a id='create-account-link' target='_blank' "
        "class='account-link' href='" + create_account_url + "'>") +
        create_account + UTF8ToUTF16("</a>");
    localized_strings->SetString("createAccountLinkHTML",
        GetStringFUTF16(IDS_SYNC_CREATE_ACCOUNT_PREFIX, create_account));
  
    string16 sync_benefits_url(
        UTF8ToUTF16(google_util::StringAppendGoogleLocaleParam(
            chrome::kSyncLearnMoreURL)));
    localized_strings->SetString("promoLearnMoreURL", sync_benefits_url);
  
    static OptionsStringResource resources[] = {
      { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE },
      { "syncSetupSpinnerTitle", IDS_SYNC_SETUP_SPINNER_TITLE },
      { "syncSetupTimeoutTitle", IDS_SYNC_SETUP_TIME_OUT_TITLE },
      { "syncSetupTimeoutContent", IDS_SYNC_SETUP_TIME_OUT_CONTENT },
      { "cannotBeBlank", IDS_SYNC_CANNOT_BE_BLANK },
      { "emailLabel", IDS_SYNC_LOGIN_EMAIL_NEW_LINE },
      { "passwordLabel", IDS_SYNC_LOGIN_PASSWORD_NEW_LINE },
      { "invalidCredentials", IDS_SYNC_INVALID_USER_CREDENTIALS },
      { "signin", IDS_SYNC_SIGNIN },
      { "couldNotConnect", IDS_SYNC_LOGIN_COULD_NOT_CONNECT },
      { "unrecoverableError", IDS_SYNC_UNRECOVERABLE_ERROR },
      { "errorLearnMore", IDS_LEARN_MORE },
      { "unrecoverableErrorHelpURL", IDS_SYNC_UNRECOVERABLE_ERROR_HELP_URL },
      { "cannotAccessAccount", IDS_SYNC_CANNOT_ACCESS_ACCOUNT },
      { "cancel", IDS_CANCEL },
      { "loginSuccess", IDS_SYNC_SUCCESS },
      { "settingUp", IDS_SYNC_LOGIN_SETTING_UP },
      { "errorSigningIn", IDS_SYNC_ERROR_SIGNING_IN },
      { "signinHeader", IDS_SYNC_PROMO_SIGNIN_HEADER},
      { "captchaInstructions", IDS_SYNC_GAIA_CAPTCHA_INSTRUCTIONS },
      { "invalidAccessCode", IDS_SYNC_INVALID_ACCESS_CODE_LABEL },
      { "enterAccessCode", IDS_SYNC_ENTER_ACCESS_CODE_LABEL },
      { "getAccessCodeHelp", IDS_SYNC_ACCESS_CODE_HELP_LABEL },
      { "getAccessCodeURL", IDS_SYNC_GET_ACCESS_CODE_URL },
      { "invalidOtp", IDS_SYNC_INVALID_OTP_LABEL },
      { "enterOtp", IDS_SYNC_ENTER_OTP_LABEL },
      { "getOtpHelp", IDS_SYNC_OTP_HELP_LABEL },
      { "getOtpURL", IDS_SYNC_GET_OTP_URL },
      { "syncAllDataTypes", IDS_SYNC_EVERYTHING },
      { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES },
      { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS },
      { "preferences", IDS_SYNC_DATATYPE_PREFERENCES },
      { "autofill", IDS_SYNC_DATATYPE_AUTOFILL },
      { "themes", IDS_SYNC_DATATYPE_THEMES },
      { "passwords", IDS_SYNC_DATATYPE_PASSWORDS },
      { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS },
      { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS },
      { "apps", IDS_SYNC_DATATYPE_APPS },
      { "openTabs", IDS_SYNC_DATATYPE_TABS },
      { "syncZeroDataTypesError", IDS_SYNC_ZERO_DATA_TYPES_ERROR },
      { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR },
      { "googleOption", IDS_SYNC_PASSPHRASE_OPT_GOOGLE },
      { "explicitOption", IDS_SYNC_PASSPHRASE_OPT_EXPLICIT },
      { "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE },
      { "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT },
      { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
      { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL },
      { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR },
      { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR },
      { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
      { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL },
      { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES },
      { "syncEverything", IDS_SYNC_SYNC_EVERYTHING },
      { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS },
      { "passphraseSectionTitle", IDS_SYNC_PASSPHRASE_SECTION_TITLE },
      { "enterPassphraseTitle", IDS_SYNC_ENTER_PASSPHRASE_TITLE },
      { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY },
      { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY },
      { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
      { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE },
      { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
      { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES },
      { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO },
      { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX },
      { "sectionExplicitMessagePostfix",
          IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX },
      { "encryptedDataTypesTitle", IDS_SYNC_ENCRYPTION_DATA_TYPES_TITLE },
      { "encryptSensitiveOption", IDS_SYNC_ENCRYPT_SENSITIVE_DATA },
      { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA },
      { "aspWarningText", IDS_SYNC_ASP_PASSWORD_WARNING_TEXT },
      { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE },
      { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON },
      { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED },
      { "promoLearnMore", IDS_LEARN_MORE },
      { "promoTitleShort", IDS_SYNC_PROMO_MESSAGE_TITLE_SHORT },
    };
  
    RegisterStrings(localized_strings, resources, arraysize(resources));
    RegisterTitle(localized_strings, "syncSetupOverlay", IDS_SYNC_SETUP_TITLE);
  }
  
  void SyncSetupHandler::DisplayConfigureSync(bool show_advanced,
                                              bool passphrase_failed) {
    ProfileSyncService* service = GetSyncService();
    if (!service->sync_initialized()) {
      // When user tries to setup sync while the sync backend is not initialized,
      // kick the sync backend and wait for it to be ready and show spinner until
      // the backend gets ready.
      retry_on_signin_failure_ = false;
  
      service->UnsuppressAndStart();
      DisplaySpinner();
      // To listen to the token available notifications, start SigninTracker.
      signin_tracker_.reset(
          new SigninTracker(GetProfile(), this,
                            SigninTracker::SERVICES_INITIALIZING));
      return;
    }
  
    // Should only be called if user is signed in, so no longer need our
    // SigninTracker.
    signin_tracker_.reset();
    configuring_sync_ = true;
    DCHECK(service->sync_initialized()) <<
        "Cannot configure sync until the sync backend is initialized";
  
    // Setup args for the sync configure screen:
    //   showSyncEverythingPage: false to skip directly to the configure screen
    //   syncAllDataTypes: true if the user wants to sync everything
    //   <data_type>Registered: true if the associated data type is supported
    //   <data_type>Synced: true if the user wants to sync that specific data type
    //   encryptionEnabled: true if sync supports encryption
    //   encryptAllData: true if user wants to encrypt all data (not just
    //       passwords)
    //   usePassphrase: true if the data is encrypted with a secondary passphrase
    //   show_passphrase: true if a passphrase is needed to decrypt the sync data
    DictionaryValue args;
  
    // Tell the UI layer which data types are registered/enabled by the user.
    const syncer::ModelTypeSet registered_types =
        service->GetRegisteredDataTypes();
    const syncer::ModelTypeSet preferred_types =
        service->GetPreferredDataTypes();
    for (size_t i = 0; i < kNumDataTypes; ++i) {
      const std::string key_name = kDataTypeNames[i];
      args.SetBoolean(key_name + "Registered",
                      registered_types.Has(kDataTypes[i]));
      args.SetBoolean(key_name + "Synced", preferred_types.Has(kDataTypes[i]));
    }
    browser_sync::SyncPrefs sync_prefs(GetProfile()->GetPrefs());
    args.SetBoolean("passphraseFailed", passphrase_failed);
    args.SetBoolean("showSyncEverythingPage", !show_advanced);
    args.SetBoolean("syncAllDataTypes", sync_prefs.HasKeepEverythingSynced());
    args.SetBoolean("encryptAllData", service->EncryptEverythingEnabled());
    args.SetBoolean("usePassphrase", service->IsUsingSecondaryPassphrase());
    // We call IsPassphraseRequired() here, instead of calling
    // IsPassphraseRequiredForDecryption(), because we want to show the passphrase
    // UI even if no encrypted data types are enabled.
    args.SetBoolean("showPassphrase", service->IsPassphraseRequired());
  
    StringValue page("configure");
    web_ui()->CallJavascriptFunction(
        "SyncSetupOverlay.showSyncSetupPage", page, args);
  }
  
  void SyncSetupHandler::ConfigureSyncDone() {
    StringValue page("done");
    web_ui()->CallJavascriptFunction(
        "SyncSetupOverlay.showSyncSetupPage", page);
  
    // Suppress the sync promo once the user signs into sync. This way the user
    // doesn't see the sync promo even if they sign out of sync later on.
    SyncPromoUI::SetUserSkippedSyncPromo(GetProfile());
  
    ProfileSyncService* service = GetSyncService();
    if (!service->HasSyncSetupCompleted()) {
      // This is the first time configuring sync, so log it.
      FilePath profile_file_path = GetProfile()->GetPath();
      ProfileMetrics::LogProfileSyncSignIn(profile_file_path);
  
      // We're done configuring, so notify ProfileSyncService that it is OK to
      // start syncing.
      service->SetSyncSetupCompleted();
    }
  }
  
  bool SyncSetupHandler::IsActiveLogin() const {
    // LoginUIService can be NULL if page is brought up in incognito mode
    // (i.e. if the user is running in guest mode in cros and brings up settings).
    LoginUIService* service = GetLoginUIService();
    return service && (service->current_login_ui() == this);
  }
  
  void SyncSetupHandler::RegisterMessages() {
    web_ui()->RegisterMessageCallback(
        "SyncSetupDidClosePage",
        base::Bind(&SyncSetupHandler::OnDidClosePage,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback(
        "SyncSetupSubmitAuth",
        base::Bind(&SyncSetupHandler::HandleSubmitAuth,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback(
        "SyncSetupConfigure",
        base::Bind(&SyncSetupHandler::HandleConfigure,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback(
        "SyncSetupAttachHandler",
        base::Bind(&SyncSetupHandler::HandleAttachHandler,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback(
        "SyncSetupShowErrorUI",
        base::Bind(&SyncSetupHandler::HandleShowErrorUI,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback(
        "SyncSetupShowSetupUI",
        base::Bind(&SyncSetupHandler::HandleShowSetupUI,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback(
        "SyncSetupShowSetupUIWithoutLogin",
        base::Bind(&SyncSetupHandler::HandleShowSetupUIWithoutLogin,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback(
        "SyncSetupDoSignOutOnAuthError",
        base::Bind(&SyncSetupHandler::HandleDoSignOutOnAuthError,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback("CloseTimeout",
        base::Bind(&SyncSetupHandler::HandleCloseTimeout,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback("SyncSetupStopSyncing",
        base::Bind(&SyncSetupHandler::HandleStopSyncing,
                   base::Unretained(this)));
  }
  
  SigninManager* SyncSetupHandler::GetSignin() const {
    return SigninManagerFactory::GetForProfile(GetProfile());
  }
  
  void SyncSetupHandler::DisplayGaiaLogin(bool fatal_error) {
    retry_on_signin_failure_ = true;
    DisplayGaiaLoginWithErrorMessage(string16(), fatal_error);
  }
  
  void SyncSetupHandler::DisplayGaiaLoginWithErrorMessage(
      const string16& error_message, bool fatal_error) {
    // We are no longer configuring sync if the login screen is visible.
    // If the user exits the signin wizard after this without configuring sync,
    // CloseSyncSetup() will ensure they are logged out.
    configuring_sync_ = false;
  
    string16 local_error_message(error_message);
  
    // Setup args for the GAIA login screen:
    //   error_message: custom error message to display.
    //   fatalError: fatal error message to display.
    //   error: GoogleServiceAuthError from previous login attempt (0 if none).
    //   user: The email the user most recently entered.
    //   editable_user: Whether the username field should be editable.
    //   captchaUrl: The captcha image to display to the user (empty if none).
    std::string user, captcha;
    int error;
    bool editable_user;
    if (!last_attempted_user_email_.empty()) {
      // This is a repeat of a login attempt.
      user = last_attempted_user_email_;
      error = last_signin_error_.state();
      captcha = last_signin_error_.captcha().image_url.spec();
      editable_user = true;
  
      if (local_error_message.empty())
        local_error_message = UTF8ToUTF16(last_signin_error_.error_message());
    } else {
      // Fresh login attempt - lock in the authenticated username if there is
      // one (don't let the user change it).
      user = GetSignin()->GetAuthenticatedUsername();
      error = 0;
      editable_user = user.empty();
    }
    DictionaryValue args;
    args.SetString("user", user);
    args.SetInteger("error", error);
  
    // If the error is two-factor, then ask for an OTP if the ClientOAuth flow
    // is enasbled.  Otherwise ask for an ASP.  If the error is catptcha required,
    // then we don't want to show username and password fields if ClientOAuth is
    // being used, since those fields are ignored by the endpoint on challenges.
    if (error == GoogleServiceAuthError::TWO_FACTOR)
      args.SetBoolean("askForOtp", false);
    else if (error == GoogleServiceAuthError::CAPTCHA_REQUIRED)
      args.SetBoolean("hideEmailAndPassword", false);
  
    args.SetBoolean("editableUser", editable_user);
    if (!local_error_message.empty())
      args.SetString("errorMessage", local_error_message);
    if (fatal_error)
      args.SetBoolean("fatalError", true);
    args.SetString("captchaUrl", captcha);
    StringValue page("login");
    web_ui()->CallJavascriptFunction(
        "SyncSetupOverlay.showSyncSetupPage", page, args);
  }
  
  bool SyncSetupHandler::PrepareSyncSetup() {
    ProfileSyncService* service = GetSyncService();
    if (!service) {
      // If there's no sync service, the user tried to manually invoke a syncSetup
      // URL, but sync features are disabled.  We need to close the overlay for
      // this (rare) case.
      DLOG(WARNING) << "Closing sync UI because sync is disabled";
      CloseOverlay();
      return false;
    }
  
    // If the wizard is already visible, just focus that one.
    if (FocusExistingWizardIfPresent()) {
      if (!IsActiveLogin())
        CloseOverlay();
      return false;
    }
  
    // Notify services that login UI is now active.
    GetLoginUIService()->SetLoginUI(this);
    service->SetSetupInProgress(true);
  
    return true;
  }
  
  void SyncSetupHandler::DisplaySpinner() {
    configuring_sync_ = true;
    StringValue page("spinner");
    DictionaryValue args;
  
    const int kTimeoutSec = 30;
    DCHECK(!backend_start_timer_.get());
    backend_start_timer_.reset(new base::OneShotTimer<SyncSetupHandler>());
    backend_start_timer_->Start(FROM_HERE,
                                base::TimeDelta::FromSeconds(kTimeoutSec),
                                this, &SyncSetupHandler::DisplayTimeout);
  
    web_ui()->CallJavascriptFunction(
        "SyncSetupOverlay.showSyncSetupPage", page, args);
  }
  
  // TODO(kochi): Handle error conditions other than timeout.
  // http://crbug.com/128692
  void SyncSetupHandler::DisplayTimeout() {
    // Stop a timer to handle timeout in waiting for checking network connection.
    backend_start_timer_.reset();
  
    // Do not listen to signin events.
    signin_tracker_.reset();
  
    StringValue page("timeout");
    DictionaryValue args;
    web_ui()->CallJavascriptFunction(
        "SyncSetupOverlay.showSyncSetupPage", page, args);
  }
  
  void SyncSetupHandler::RecordSignin() {
    // By default, do nothing - subclasses can override.
  }
  
  void SyncSetupHandler::DisplayGaiaSuccessAndClose() {
    RecordSignin();
    web_ui()->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndClose");
  }
  
  void SyncSetupHandler::DisplayGaiaSuccessAndSettingUp() {
    RecordSignin();
    web_ui()->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndSettingUp");
  }
  
  void SyncSetupHandler::OnDidClosePage(const ListValue* args) {
    CloseSyncSetup();
  }
  
  void SyncSetupHandler::HandleSubmitAuth(const ListValue* args) {
    std::string json;
    if (!args->GetString(0, &json)) {
      NOTREACHED() << "Could not read JSON argument";
      return;
    }
  
    if (json.empty())
      return;
  
    std::string username, password, captcha, otp, access_code;
    if (!GetAuthData(json, &username, &password, &captcha, &otp, &access_code)) {
      // The page sent us something that we didn't understand.
      // This probably indicates a programming error.
      NOTREACHED();
      return;
    }
  
    string16 error_message;
    if (!IsLoginAuthDataValid(username, &error_message)) {
      DisplayGaiaLoginWithErrorMessage(error_message, false);
      return;
    }
  
    // If one of password, captcha, otp and access_code is non-empty, then the
    // others must be empty.  At least one should be non-empty.
    DCHECK(password.empty() ||
           (captcha.empty() && otp.empty() && access_code.empty()));
    DCHECK(captcha.empty() ||
           (password.empty() && otp.empty() && access_code.empty()));
    DCHECK(otp.empty() ||
           (captcha.empty() && password.empty() && access_code.empty()));
    DCHECK(access_code.empty() ||
           (captcha.empty() && password.empty() && otp.empty()));
    DCHECK(!otp.empty() || !captcha.empty() || !password.empty() ||
           !access_code.empty());
  
    const std::string& solution = captcha.empty() ?
        (otp.empty() ? EmptyString() : otp) : captcha;
    TryLogin(username, password, solution, access_code);
  }
  
  void SyncSetupHandler::TryLogin(const std::string& username,
                                  const std::string& password,
                                  const std::string& solution,
                                  const std::string& access_code) {
    DCHECK(IsActiveLogin());
    // Make sure we are listening for signin traffic.
    if (!signin_tracker_.get())
      signin_tracker_.reset(new SigninTracker(GetProfile(), this));
  
    last_attempted_user_email_ = username;
  
    // User is trying to log in again so reset the cached error.
    GoogleServiceAuthError current_error = last_signin_error_;
    last_signin_error_ = GoogleServiceAuthError::None();
  
    SigninManager* signin = GetSignin();
  
    // If we're just being called to provide an ASP, then pass it to the
    // SigninManager and wait for the next step.
    if (!access_code.empty()) {
      signin->ProvideSecondFactorAccessCode(access_code);
      return;
    }
  
    // The user has submitted credentials, which indicates they don't want to
    // suppress start up anymore. We do this before starting the signin process,
    // so the ProfileSyncService knows to listen to the cached password.
    GetSyncService()->UnsuppressAndStart();
  
    // Kick off a sign-in through the signin manager.
    signin->StartSignIn(username, password, current_error.captcha().token,
                        solution);
  }
  
  void SyncSetupHandler::GaiaCredentialsValid() {
    DCHECK(IsActiveLogin());
  
    // Gaia credentials are valid - update the UI.
    DisplayGaiaSuccessAndSettingUp();
  }
  
  void SyncSetupHandler::SigninFailed(const GoogleServiceAuthError& error) {
    // Stop a timer to handle timeout in waiting for checking network connection.
    backend_start_timer_.reset();
  
    last_signin_error_ = error;
    // Got a failed signin - this is either just a typical auth error, or a
    // sync error (treat sync errors as "fatal errors" - i.e. non-auth errors).
    // On ChromeOS, this condition can happen when auth token is invalid and
    // cannot start sync backend.
    if (retry_on_signin_failure_) {
      DisplayGaiaLogin(GetSyncService()->HasUnrecoverableError());
    } else {
      // TODO(peria): Show error dialog for prompting sign in and out on
      // Chrome OS. http://crbug.com/128692
      CloseOverlay();
    }
  }
  
  Profile* SyncSetupHandler::GetProfile() const {
    return Profile::FromWebUI(web_ui());
  }
  
  ProfileSyncService* SyncSetupHandler::GetSyncService() const {
    return ProfileSyncServiceFactory::GetForProfile(GetProfile());
  }
  
  void SyncSetupHandler::SigninSuccess() {
    DCHECK(GetSyncService()->sync_initialized());
    // Stop a timer to handle timeout in waiting for checking network connection.
    backend_start_timer_.reset();
  
    // If we have signed in while sync is already setup, it must be due to some
    // kind of re-authentication flow. In that case, just close the signin dialog
    // rather than forcing the user to go through sync configuration.
    if (GetSyncService()->HasSyncSetupCompleted())
      DisplayGaiaSuccessAndClose();
    else
      DisplayConfigureSync(false, false);
  }
  
  void SyncSetupHandler::HandleConfigure(const ListValue* args) {
    std::string json;
    if (!args->GetString(0, &json)) {
      NOTREACHED() << "Could not read JSON argument";
      return;
    }
    if (json.empty()) {
      NOTREACHED();
      return;
    }
  
    SyncConfigInfo configuration;
    if (!GetConfiguration(json, &configuration)) {
      // The page sent us something that we didn't understand.
      // This probably indicates a programming error.
      NOTREACHED();
      return;
    }
  
    // Start configuring the ProfileSyncService using the configuration passed
    // to us from the JS layer.
    ProfileSyncService* service = GetSyncService();
  
    // If the sync engine has shutdown for some reason, just close the sync
    // dialog.
    if (!service->sync_initialized()) {
      CloseOverlay();
      return;
    }
  
    // Note: Data encryption will not occur until configuration is complete
    // (when the PSS receives its CONFIGURE_DONE notification from the sync
    // backend), so the user still has a chance to cancel out of the operation
    // if (for example) some kind of passphrase error is encountered.
    if (configuration.encrypt_all)
      service->EnableEncryptEverything();
  
    bool passphrase_failed = false;
    if (!configuration.passphrase.empty()) {
      // We call IsPassphraseRequired() here (instead of
      // IsPassphraseRequiredForDecryption()) because the user may try to enter
      // a passphrase even though no encrypted data types are enabled.
      if (service->IsPassphraseRequired()) {
        // If we have pending keys, try to decrypt them with the provided
        // passphrase. We track if this succeeds or fails because a failed
        // decryption should result in an error even if there aren't any encrypted
        // data types.
        passphrase_failed =
            !service->SetDecryptionPassphrase(configuration.passphrase);
      } else {
        // OK, the user sent us a passphrase, but we don't have pending keys. So
        // it either means that the pending keys were resolved somehow since the
        // time the UI was displayed (re-encryption, pending passphrase change,
        // etc) or the user wants to re-encrypt.
        if (!configuration.passphrase_is_gaia &&
            !service->IsUsingSecondaryPassphrase()) {
          // User passed us a secondary passphrase, and the data is encrypted
          // with a GAIA passphrase so they must want to encrypt.
          service->SetEncryptionPassphrase(configuration.passphrase,
                                           ProfileSyncService::EXPLICIT);
        }
      }
    }
  
    bool user_was_prompted_for_passphrase =
        service->IsPassphraseRequiredForDecryption();
    service->OnUserChoseDatatypes(configuration.sync_everything,
                                  configuration.data_types);
  
    // Need to call IsPassphraseRequiredForDecryption() *after* calling
    // OnUserChoseDatatypes() because the user may have just disabled the
    // encrypted datatypes (in which case we just want to exit, not prompt the
    // user for a passphrase).
    if (passphrase_failed || service->IsPassphraseRequiredForDecryption()) {
      // We need a passphrase, or the user's attempt to set a passphrase failed -
      // prompt them again. This covers a few subtle cases:
      // 1) The user enters an incorrect passphrase *and* disabled the encrypted
      //    data types. In that case we want to notify the user that the
      //    passphrase was incorrect even though there are no longer any encrypted
      //    types enabled (IsPassphraseRequiredForDecryption() == false).
      // 2) The user doesn't enter any passphrase. In this case, we won't call
      //    SetDecryptionPassphrase() (passphrase_failed == false), but we still
      //    want to display an error message to let the user know that their
      //    blank passphrase entry is not acceptable.
      // 3) The user just enabled an encrypted data type - in this case we don't
      //    want to display an "invalid passphrase" error, since it's the first
      //    time the user is seeing the prompt.
      DisplayConfigureSync(
          true, passphrase_failed || user_was_prompted_for_passphrase);
    } else {
      // No passphrase is required from the user so mark the configuration as
      // complete and close the sync setup overlay.
      ConfigureSyncDone();
    }
  
    ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE);
    if (configuration.encrypt_all)
      ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT);
    if (configuration.passphrase_is_gaia && !configuration.passphrase.empty())
      ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE);
    if (!configuration.sync_everything)
      ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE);
  }
  
  void SyncSetupHandler::HandleAttachHandler(const ListValue* args) {
    bool force_login = false;
    std::string json;
    if (args->GetString(0, &json) && !json.empty()) {
      scoped_ptr<Value> parsed_value(base::JSONReader::Read(json));
      DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
      result->GetBoolean("forceLogin", &force_login);
    }
  
    OpenSyncSetup(force_login);
  }
  
  void SyncSetupHandler::HandleShowErrorUI(const ListValue* args) {
    DCHECK(!configuring_sync_);
  
    ProfileSyncService* service = GetSyncService();
    DCHECK(service);
  
    // Bring up the existing wizard, or just display it on this page.
    if (!FocusExistingWizardIfPresent())
      OpenSyncSetup(false);
  }
  
  void SyncSetupHandler::HandleShowSetupUI(const ListValue* args) {
    OpenSyncSetup(false);
  }
  
  void SyncSetupHandler::HandleShowSetupUIWithoutLogin(const ListValue* args) {
    OpenConfigureSync();
  }
  
  void SyncSetupHandler::HandleDoSignOutOnAuthError(const ListValue* args) {
    DLOG(INFO) << "Signing out the user to fix a sync error.";
    browser::AttemptUserExit();
  }
  
  void SyncSetupHandler::HandleStopSyncing(const ListValue* args) {
    ProfileSyncService* service = GetSyncService();
    DCHECK(service);
  
    if (ProfileSyncService::IsSyncEnabled()) {
      service->DisableForUser();
      ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS);
    }
  }
  
  void SyncSetupHandler::HandleCloseTimeout(const ListValue* args) {
    CloseSyncSetup();
  }
  
  void SyncSetupHandler::CloseSyncSetup() {
    // TODO(atwilson): Move UMA tracking of signin events out of sync module.
    ProfileSyncService* sync_service = GetSyncService();
    if (IsActiveLogin()) {
      if (!sync_service->HasSyncSetupCompleted()) {
        if (signin_tracker_.get()) {
          ProfileSyncService::SyncEvent(
              ProfileSyncService::CANCEL_DURING_SIGNON);
        } else if (configuring_sync_) {
          ProfileSyncService::SyncEvent(
              ProfileSyncService::CANCEL_DURING_CONFIGURE);
        } else {
          ProfileSyncService::SyncEvent(
              ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH);
        }
      }
      // Let the various services know that we're no longer active.
      GetLoginUIService()->LoginUIClosed(this);
    }
  
    if (sync_service) {
      sync_service->SetSetupInProgress(false);
  
      // Make sure user isn't left half-logged-in (signed in, but without sync
      // started up). If the user hasn't finished setting up sync, then sign out
      // and shut down sync.
      if (!sync_service->HasSyncSetupCompleted()) {
        DVLOG(1) << "Signin aborted by user action";
        sync_service->DisableForUser();
  
        browser_sync::SyncPrefs sync_prefs(GetProfile()->GetPrefs());
        sync_prefs.SetStartSuppressed(true);
      }
    }
  
    // Reset the attempted email address and error, otherwise the sync setup
    // overlay in the settings page will stay in whatever error state it was last
    // when it is reopened.
    last_attempted_user_email_.clear();
    last_signin_error_ = GoogleServiceAuthError::None();
  
    configuring_sync_ = false;
    signin_tracker_.reset();
  
    // Stop a timer to handle timeout in waiting for checking network connection.
    backend_start_timer_.reset();
  }
  
  void SyncSetupHandler::OpenSyncSetup(bool force_login) {
    if (!PrepareSyncSetup())
      return;
  
    ProfileSyncService* service = GetSyncService();
  
    // There are several different UI flows that can bring the user here:
    // 1) Signin promo (passes force_login=true)
    // 2) Normal signin through options page (IsSyncEnabledAndLoggedIn() will
    //    return false).
    // 3) Previously working credentials have expired
    //    (service->GetAuthError() != NONE).
    // 4) User is already signed in, but App Notifications needs to force another
    //    login so it can fetch an oauth token (passes force_login=true)
    // 5) User clicks [Advanced Settings] button on options page while already
    //    logged in.
    // 6) One-click signin (credentials are already available, so should display
    //    sync configure UI, not login UI).
    if (force_login ||
        !service->IsSyncEnabledAndLoggedIn() ||
        service->GetAuthError().state() != GoogleServiceAuthError::NONE) {
      // User is not logged in, or login has been specially requested - need to
      // display login UI (cases 1-4).
      DisplayGaiaLogin(false);
    } else {
      // User is already logged in. They must have brought up the config wizard
      // via the "Advanced..." button or through One-Click signin (cases 5/6).
      DisplayConfigureSync(true, false);
    }
  
    ShowSetupUI();
  }
  
  void SyncSetupHandler::OpenConfigureSync() {
    if (!PrepareSyncSetup())
      return;
  
    DisplayConfigureSync(true, false);
    ShowSetupUI();
  }
  
  void SyncSetupHandler::FocusUI() {
    DCHECK(IsActiveLogin());
    WebContents* web_contents = web_ui()->GetWebContents();
    web_contents->GetDelegate()->ActivateContents(web_contents);
  }
  
  void SyncSetupHandler::CloseUI() {
    DCHECK(IsActiveLogin());
    CloseOverlay();
  }
  
  // Private member functions.
  
  bool SyncSetupHandler::FocusExistingWizardIfPresent() {
    LoginUIService* service = GetLoginUIService();
    if (!service->current_login_ui())
      return false;
    service->current_login_ui()->FocusUI();
    return true;
  }
  
  LoginUIService* SyncSetupHandler::GetLoginUIService() const {
    return LoginUIServiceFactory::GetForProfile(GetProfile());
  }
  
  void SyncSetupHandler::CloseOverlay() {
    // Stop a timer to handle timeout in waiting for sync setup.
    backend_start_timer_.reset();
  
    CloseSyncSetup();
    web_ui()->CallJavascriptFunction("OptionsPage.closeOverlay");
  }
  
  bool SyncSetupHandler::IsLoginAuthDataValid(const std::string& username,
                                              string16* error_message) {
    if (username.empty())
      return true;
  
    // Can be null during some unit tests.
    if (!web_ui())
      return true;
  
    if (!GetSignin()->IsAllowedUsername(username)) {
      *error_message = l10n_util::GetStringUTF16(IDS_SYNC_LOGIN_NAME_PROHIBITED);
      return false;
    }
  
    // If running in a unit test, skip profile check.
    if (!profile_manager_)
      return true;
  
    // Check if the username is already in use by another profile.
    const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
    size_t current_profile_index =
        cache.GetIndexOfProfileWithPath(GetProfile()->GetPath());
    string16 username_utf16 = UTF8ToUTF16(username);
  
    for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
      if (i != current_profile_index && AreUserNamesEqual(
          cache.GetUserNameOfProfileAtIndex(i), username_utf16)) {
        *error_message = l10n_util::GetStringUTF16(
            IDS_SYNC_USER_NAME_IN_USE_ERROR);
        return false;
      }
    }
  
    return true;
  }
  