07a2f30b8d5d46939b6bf07f1dd9cd6d8978d15d
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / sync_setup_handler.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/webui/sync_setup_handler.h"
6
7 #include "base/basictypes.h"
8 #include "base/bind.h"
9 #include "base/bind_helpers.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/i18n/time_formatting.h"
13 #include "base/json/json_reader.h"
14 #include "base/json/json_writer.h"
15 #include "base/metrics/histogram.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "chrome/app/chrome_command_ids.h"
20 #include "chrome/browser/google/google_util.h"
21 #include "chrome/browser/lifetime/application_lifetime.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/profiles/profile_info_cache.h"
24 #include "chrome/browser/profiles/profile_manager.h"
25 #include "chrome/browser/profiles/profile_metrics.h"
26 #include "chrome/browser/signin/signin_global_error.h"
27 #include "chrome/browser/signin/signin_manager_factory.h"
28 #include "chrome/browser/signin/signin_promo.h"
29 #include "chrome/browser/sync/profile_sync_service.h"
30 #include "chrome/browser/sync/profile_sync_service_factory.h"
31 #include "chrome/browser/ui/browser_finder.h"
32 #include "chrome/browser/ui/browser_navigator.h"
33 #include "chrome/browser/ui/sync/signin_histogram.h"
34 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
35 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
36 #include "chrome/common/chrome_switches.h"
37 #include "chrome/common/pref_names.h"
38 #include "chrome/common/url_constants.h"
39 #include "content/public/browser/render_view_host.h"
40 #include "content/public/browser/web_contents.h"
41 #include "content/public/browser/web_contents_delegate.h"
42 #include "google_apis/gaia/gaia_auth_util.h"
43 #include "google_apis/gaia/gaia_constants.h"
44 #include "grit/chromium_strings.h"
45 #include "grit/generated_resources.h"
46 #include "grit/locale_settings.h"
47 #include "net/base/url_util.h"
48 #include "ui/base/l10n/l10n_util.h"
49
50 #if defined(OS_CHROMEOS)
51 #include "chrome/browser/signin/signin_manager_base.h"
52 #else
53 #include "chrome/browser/signin/signin_manager.h"
54 #endif
55
56 using content::WebContents;
57 using l10n_util::GetStringFUTF16;
58 using l10n_util::GetStringUTF16;
59
60 namespace {
61
62 // A structure which contains all the configuration information for sync.
63 struct SyncConfigInfo {
64   SyncConfigInfo();
65   ~SyncConfigInfo();
66
67   bool encrypt_all;
68   bool sync_everything;
69   bool sync_nothing;
70   syncer::ModelTypeSet data_types;
71   std::string passphrase;
72   bool passphrase_is_gaia;
73 };
74
75 SyncConfigInfo::SyncConfigInfo()
76     : encrypt_all(false),
77       sync_everything(false),
78       sync_nothing(false),
79       passphrase_is_gaia(false) {
80 }
81
82 SyncConfigInfo::~SyncConfigInfo() {}
83
84 // Note: The order of these types must match the ordering of
85 // the respective types in ModelType
86 const char* kDataTypeNames[] = {
87   "bookmarks",
88   "preferences",
89   "passwords",
90   "autofill",
91   "themes",
92   "typedUrls",
93   "extensions",
94   "apps",
95   "tabs"
96 };
97
98 COMPILE_ASSERT(31 == syncer::MODEL_TYPE_COUNT,
99                update_kDataTypeNames_to_match_UserSelectableTypes);
100
101 typedef std::map<syncer::ModelType, const char*> ModelTypeNameMap;
102
103 ModelTypeNameMap GetSelectableTypeNameMap() {
104   ModelTypeNameMap type_names;
105   syncer::ModelTypeSet type_set = syncer::UserSelectableTypes();
106   syncer::ModelTypeSet::Iterator it = type_set.First();
107   DCHECK_EQ(arraysize(kDataTypeNames), type_set.Size());
108   for (size_t i = 0; i < arraysize(kDataTypeNames) && it.Good();
109        ++i, it.Inc()) {
110     type_names[it.Get()] = kDataTypeNames[i];
111   }
112   return type_names;
113 }
114
115 bool GetConfiguration(const std::string& json, SyncConfigInfo* config) {
116   scoped_ptr<base::Value> parsed_value(base::JSONReader::Read(json));
117   base::DictionaryValue* result;
118   if (!parsed_value || !parsed_value->GetAsDictionary(&result)) {
119     DLOG(ERROR) << "GetConfiguration() not passed a Dictionary";
120     return false;
121   }
122
123   if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything)) {
124     DLOG(ERROR) << "GetConfiguration() not passed a syncAllDataTypes value";
125     return false;
126   }
127
128   if (!result->GetBoolean("syncNothing", &config->sync_nothing)) {
129     DLOG(ERROR) << "GetConfiguration() not passed a syncNothing value";
130     return false;
131   }
132
133   DCHECK(!(config->sync_everything && config->sync_nothing))
134       << "syncAllDataTypes and syncNothing cannot both be true";
135
136   ModelTypeNameMap type_names = GetSelectableTypeNameMap();
137
138   for (ModelTypeNameMap::const_iterator it = type_names.begin();
139        it != type_names.end(); ++it) {
140     std::string key_name = it->second + std::string("Synced");
141     bool sync_value;
142     if (!result->GetBoolean(key_name, &sync_value)) {
143       DLOG(ERROR) << "GetConfiguration() not passed a value for " << key_name;
144       return false;
145     }
146     if (sync_value)
147       config->data_types.Put(it->first);
148   }
149
150   // Encryption settings.
151   if (!result->GetBoolean("encryptAllData", &config->encrypt_all)) {
152     DLOG(ERROR) << "GetConfiguration() not passed a value for encryptAllData";
153     return false;
154   }
155
156   // Passphrase settings.
157   bool have_passphrase;
158   if (!result->GetBoolean("usePassphrase", &have_passphrase)) {
159     DLOG(ERROR) << "GetConfiguration() not passed a usePassphrase value";
160     return false;
161   }
162
163   if (have_passphrase) {
164     if (!result->GetBoolean("isGooglePassphrase",
165                             &config->passphrase_is_gaia)) {
166       DLOG(ERROR) << "GetConfiguration() not passed isGooglePassphrase value";
167       return false;
168     }
169     if (!result->GetString("passphrase", &config->passphrase)) {
170       DLOG(ERROR) << "GetConfiguration() not passed a passphrase value";
171       return false;
172     }
173   }
174   return true;
175 }
176
177 }  // namespace
178
179 SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager)
180     : configuring_sync_(false),
181       profile_manager_(profile_manager) {
182 }
183
184 SyncSetupHandler::~SyncSetupHandler() {
185   // Just exit if running unit tests (no actual WebUI is attached).
186   if (!web_ui())
187     return;
188
189   // This case is hit when the user performs a back navigation.
190   CloseSyncSetup();
191 }
192
193 void SyncSetupHandler::GetLocalizedValues(
194     base::DictionaryValue* localized_strings) {
195   GetStaticLocalizedValues(localized_strings, web_ui());
196 }
197
198 void SyncSetupHandler::GetStaticLocalizedValues(
199     base::DictionaryValue* localized_strings,
200     content::WebUI* web_ui) {
201   DCHECK(localized_strings);
202
203   base::string16 product_name(GetStringUTF16(IDS_PRODUCT_NAME));
204   localized_strings->SetString(
205       "chooseDataTypesInstructions",
206       GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, product_name));
207   localized_strings->SetString(
208       "encryptionInstructions",
209       GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, product_name));
210   localized_strings->SetString(
211       "encryptionHelpURL", chrome::kSyncEncryptionHelpURL);
212   localized_strings->SetString(
213       "encryptionSectionMessage",
214       GetStringFUTF16(IDS_SYNC_ENCRYPTION_SECTION_MESSAGE, product_name));
215   localized_strings->SetString(
216       "passphraseRecover",
217       GetStringFUTF16(IDS_SYNC_PASSPHRASE_RECOVER,
218                       base::ASCIIToUTF16(
219                           google_util::StringAppendGoogleLocaleParam(
220                               chrome::kSyncGoogleDashboardURL))));
221   localized_strings->SetString("stopSyncingExplanation",
222       l10n_util::GetStringFUTF16(
223           IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL,
224           l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
225           base::ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
226               chrome::kSyncGoogleDashboardURL))));
227   localized_strings->SetString("stopSyncingTitle",
228       l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE));
229   localized_strings->SetString("stopSyncingConfirm",
230         l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL));
231
232   localized_strings->SetString(
233       "syncEverythingHelpURL", chrome::kSyncEverythingLearnMoreURL);
234   localized_strings->SetString(
235       "syncErrorHelpURL", chrome::kSyncErrorsHelpURL);
236
237   static OptionsStringResource resources[] = {
238     { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE },
239     { "syncSetupSpinnerTitle", IDS_SYNC_SETUP_SPINNER_TITLE },
240     { "syncSetupTimeoutTitle", IDS_SYNC_SETUP_TIME_OUT_TITLE },
241     { "syncSetupTimeoutContent", IDS_SYNC_SETUP_TIME_OUT_CONTENT },
242     { "errorLearnMore", IDS_LEARN_MORE },
243     { "cancel", IDS_CANCEL },
244     { "loginSuccess", IDS_SYNC_SUCCESS },
245     { "settingUp", IDS_SYNC_LOGIN_SETTING_UP },
246     { "syncAllDataTypes", IDS_SYNC_EVERYTHING },
247     { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES },
248     { "syncNothing", IDS_SYNC_NOTHING },
249     { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS },
250     { "preferences", IDS_SYNC_DATATYPE_PREFERENCES },
251     { "autofill", IDS_SYNC_DATATYPE_AUTOFILL },
252     { "themes", IDS_SYNC_DATATYPE_THEMES },
253     { "passwords", IDS_SYNC_DATATYPE_PASSWORDS },
254     { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS },
255     { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS },
256     { "apps", IDS_SYNC_DATATYPE_APPS },
257     { "openTabs", IDS_SYNC_DATATYPE_TABS },
258     { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR },
259     { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL },
260     { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR },
261     { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR },
262     { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL },
263     { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES },
264     { "syncEverything", IDS_SYNC_SYNC_EVERYTHING },
265     { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS },
266     { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY },
267     { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY },
268     { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
269     { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE },
270     { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
271     { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES },
272     { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO },
273     { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX },
274     { "sectionExplicitMessagePostfix",
275         IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX },
276     // TODO(rogerta): browser/resource/sync_promo/sync_promo.html and related
277     // file may not be needed any more.  If not, then the following promo
278     // strings can also be removed.
279     { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE },
280     { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON },
281     { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED },
282     { "promoLearnMore", IDS_LEARN_MORE },
283     { "promoTitleShort", IDS_SYNC_PROMO_MESSAGE_TITLE_SHORT },
284     { "encryptionSectionTitle", IDS_SYNC_ENCRYPTION_SECTION_TITLE },
285     { "basicEncryptionOption", IDS_SYNC_BASIC_ENCRYPTION_DATA },
286     { "fullEncryptionOption", IDS_SYNC_FULL_ENCRYPTION_DATA },
287   };
288
289   RegisterStrings(localized_strings, resources, arraysize(resources));
290   RegisterTitle(localized_strings, "syncSetupOverlay", IDS_SYNC_SETUP_TITLE);
291 }
292
293 void SyncSetupHandler::DisplayConfigureSync(bool show_advanced,
294                                             bool passphrase_failed) {
295   // Should never call this when we are not signed in.
296   DCHECK(!SigninManagerFactory::GetForProfile(
297       GetProfile())->GetAuthenticatedUsername().empty());
298   ProfileSyncService* service = GetSyncService();
299   DCHECK(service);
300   if (!service->sync_initialized()) {
301     service->UnsuppressAndStart();
302
303     // See if it's even possible to bring up the sync backend - if not
304     // (unrecoverable error?), don't bother displaying a spinner that will be
305     // immediately closed because this leads to some ugly infinite UI loop (see
306     // http://crbug.com/244769).
307     if (SyncStartupTracker::GetSyncServiceState(GetProfile()) !=
308         SyncStartupTracker::SYNC_STARTUP_ERROR) {
309       DisplaySpinner();
310     }
311
312     // Start SyncSetupTracker to wait for sync to initialize.
313     sync_startup_tracker_.reset(
314         new SyncStartupTracker(GetProfile(), this));
315     return;
316   }
317
318   // Should only get here if user is signed in and sync is initialized, so no
319   // longer need a SyncStartupTracker.
320   sync_startup_tracker_.reset();
321   configuring_sync_ = true;
322   DCHECK(service->sync_initialized()) <<
323       "Cannot configure sync until the sync backend is initialized";
324
325   // Setup args for the sync configure screen:
326   //   showSyncEverythingPage: false to skip directly to the configure screen
327   //   syncAllDataTypes: true if the user wants to sync everything
328   //   syncNothing: true if the user wants to sync nothing
329   //   <data_type>Registered: true if the associated data type is supported
330   //   <data_type>Synced: true if the user wants to sync that specific data type
331   //   encryptionEnabled: true if sync supports encryption
332   //   encryptAllData: true if user wants to encrypt all data (not just
333   //       passwords)
334   //   usePassphrase: true if the data is encrypted with a secondary passphrase
335   //   show_passphrase: true if a passphrase is needed to decrypt the sync data
336   base::DictionaryValue args;
337
338   // Tell the UI layer which data types are registered/enabled by the user.
339   const syncer::ModelTypeSet registered_types =
340       service->GetRegisteredDataTypes();
341   const syncer::ModelTypeSet preferred_types =
342       service->GetPreferredDataTypes();
343   ModelTypeNameMap type_names = GetSelectableTypeNameMap();
344   for (ModelTypeNameMap::const_iterator it = type_names.begin();
345        it != type_names.end(); ++it) {
346     syncer::ModelType sync_type = it->first;
347     const std::string key_name = it->second;
348     args.SetBoolean(key_name + "Registered",
349                     registered_types.Has(sync_type));
350     args.SetBoolean(key_name + "Synced", preferred_types.Has(sync_type));
351   }
352   browser_sync::SyncPrefs sync_prefs(GetProfile()->GetPrefs());
353   args.SetBoolean("passphraseFailed", passphrase_failed);
354   args.SetBoolean("showSyncEverythingPage", !show_advanced);
355   args.SetBoolean("syncAllDataTypes", sync_prefs.HasKeepEverythingSynced());
356   args.SetBoolean("syncNothing", false);  // Always false during initial setup.
357   args.SetBoolean("encryptAllData", service->EncryptEverythingEnabled());
358
359   // We call IsPassphraseRequired() here, instead of calling
360   // IsPassphraseRequiredForDecryption(), because we want to show the passphrase
361   // UI even if no encrypted data types are enabled.
362   args.SetBoolean("showPassphrase", service->IsPassphraseRequired());
363
364   // To distinguish between FROZEN_IMPLICIT_PASSPHRASE and CUSTOM_PASSPHRASE
365   // we only set usePassphrase for CUSTOM_PASSPHRASE.
366   args.SetBoolean("usePassphrase",
367                   service->GetPassphraseType() == syncer::CUSTOM_PASSPHRASE);
368   base::Time passphrase_time = service->GetExplicitPassphraseTime();
369   syncer::PassphraseType passphrase_type = service->GetPassphraseType();
370   if (!passphrase_time.is_null()) {
371     base::string16 passphrase_time_str =
372         base::TimeFormatShortDate(passphrase_time);
373     args.SetString(
374         "enterPassphraseBody",
375         GetStringFUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE,
376                         passphrase_time_str));
377     args.SetString(
378         "enterGooglePassphraseBody",
379         GetStringFUTF16(IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY_WITH_DATE,
380                         passphrase_time_str));
381     switch (passphrase_type) {
382       case syncer::FROZEN_IMPLICIT_PASSPHRASE:
383         args.SetString(
384             "fullEncryptionBody",
385             GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_GOOGLE_WITH_DATE,
386                             passphrase_time_str));
387         break;
388       case syncer::CUSTOM_PASSPHRASE:
389         args.SetString(
390             "fullEncryptionBody",
391             GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM_WITH_DATE,
392                             passphrase_time_str));
393         break;
394       default:
395         args.SetString(
396             "fullEncryptionBody",
397             GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
398         break;
399     }
400   } else if (passphrase_type == syncer::CUSTOM_PASSPHRASE) {
401     args.SetString(
402         "fullEncryptionBody",
403         GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
404   } else {
405     args.SetString(
406         "fullEncryptionBody",
407         GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_DATA));
408   }
409
410   base::StringValue page("configure");
411   web_ui()->CallJavascriptFunction(
412       "SyncSetupOverlay.showSyncSetupPage", page, args);
413
414   // Make sure the tab used for the Gaia sign in does not cover the settings
415   // tab.
416   FocusUI();
417 }
418
419 void SyncSetupHandler::ConfigureSyncDone() {
420   base::StringValue page("done");
421   web_ui()->CallJavascriptFunction(
422       "SyncSetupOverlay.showSyncSetupPage", page);
423
424   // Suppress the sign in promo once the user starts sync. This way the user
425   // doesn't see the sign in promo even if they sign out later on.
426   signin::SetUserSkippedPromo(GetProfile());
427
428   ProfileSyncService* service = GetSyncService();
429   DCHECK(service);
430   if (!service->HasSyncSetupCompleted()) {
431     // This is the first time configuring sync, so log it.
432     base::FilePath profile_file_path = GetProfile()->GetPath();
433     ProfileMetrics::LogProfileSyncSignIn(profile_file_path);
434
435     // We're done configuring, so notify ProfileSyncService that it is OK to
436     // start syncing.
437     service->SetSetupInProgress(false);
438     service->SetSyncSetupCompleted();
439   }
440 }
441
442 bool SyncSetupHandler::IsActiveLogin() const {
443   // LoginUIService can be NULL if page is brought up in incognito mode
444   // (i.e. if the user is running in guest mode in cros and brings up settings).
445   LoginUIService* service = GetLoginUIService();
446   return service && (service->current_login_ui() == this);
447 }
448
449 void SyncSetupHandler::RegisterMessages() {
450   web_ui()->RegisterMessageCallback(
451       "SyncSetupDidClosePage",
452       base::Bind(&SyncSetupHandler::OnDidClosePage,
453                  base::Unretained(this)));
454   web_ui()->RegisterMessageCallback(
455       "SyncSetupConfigure",
456       base::Bind(&SyncSetupHandler::HandleConfigure,
457                  base::Unretained(this)));
458   web_ui()->RegisterMessageCallback(
459       "SyncSetupShowSetupUI",
460       base::Bind(&SyncSetupHandler::HandleShowSetupUI,
461                  base::Unretained(this)));
462   web_ui()->RegisterMessageCallback("CloseTimeout",
463       base::Bind(&SyncSetupHandler::HandleCloseTimeout,
464                  base::Unretained(this)));
465 #if defined(OS_CHROMEOS)
466   web_ui()->RegisterMessageCallback(
467       "SyncSetupDoSignOutOnAuthError",
468       base::Bind(&SyncSetupHandler::HandleDoSignOutOnAuthError,
469                  base::Unretained(this)));
470 #else
471   web_ui()->RegisterMessageCallback("SyncSetupStopSyncing",
472       base::Bind(&SyncSetupHandler::HandleStopSyncing,
473                  base::Unretained(this)));
474   web_ui()->RegisterMessageCallback("SyncSetupStartSignIn",
475       base::Bind(&SyncSetupHandler::HandleStartSignin,
476                  base::Unretained(this)));
477 #endif
478 }
479
480 #if !defined(OS_CHROMEOS)
481 void SyncSetupHandler::DisplayGaiaLogin() {
482   DCHECK(!sync_startup_tracker_);
483   // Advanced options are no longer being configured if the login screen is
484   // visible. If the user exits the signin wizard after this without
485   // configuring sync, CloseSyncSetup() will ensure they are logged out.
486   configuring_sync_ = false;
487   DisplayGaiaLoginInNewTabOrWindow();
488 }
489
490 void SyncSetupHandler::DisplayGaiaLoginInNewTabOrWindow() {
491   GURL url(signin::GetPromoURL(signin::SOURCE_SETTINGS,
492                                true));  // auto close after success.
493   Browser* browser = chrome::FindBrowserWithWebContents(
494       web_ui()->GetWebContents());
495   if (!browser) {
496     // Settings is not displayed in a browser window. Open a new window.
497     browser = new Browser(Browser::CreateParams(
498         Browser::TYPE_TABBED, GetProfile(), chrome::GetActiveDesktop()));
499   }
500
501   // If the signin manager already has an authenticated username, this is a
502   // re-auth scenario, and we need to ensure that the user signs in with the
503   // same email address.
504   std::string email = SigninManagerFactory::GetForProfile(
505       browser->profile())->GetAuthenticatedUsername();
506   if (!email.empty()) {
507     UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
508                               signin::HISTOGRAM_SHOWN,
509                               signin::HISTOGRAM_MAX);
510     url = net::AppendQueryParameter(url, "Email", email);
511   }
512
513   browser->OpenURL(
514       content::OpenURLParams(url, content::Referrer(), SINGLETON_TAB,
515                              content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
516 }
517 #endif
518
519 bool SyncSetupHandler::PrepareSyncSetup() {
520
521   // If the wizard is already visible, just focus that one.
522   if (FocusExistingWizardIfPresent()) {
523     if (!IsActiveLogin())
524       CloseSyncSetup();
525     return false;
526   }
527
528   // Notify services that login UI is now active.
529   GetLoginUIService()->SetLoginUI(this);
530
531   ProfileSyncService* service = GetSyncService();
532   if (service)
533     service->SetSetupInProgress(true);
534
535   return true;
536 }
537
538 void SyncSetupHandler::DisplaySpinner() {
539   configuring_sync_ = true;
540   base::StringValue page("spinner");
541   base::DictionaryValue args;
542
543   const int kTimeoutSec = 30;
544   DCHECK(!backend_start_timer_);
545   backend_start_timer_.reset(new base::OneShotTimer<SyncSetupHandler>());
546   backend_start_timer_->Start(FROM_HERE,
547                               base::TimeDelta::FromSeconds(kTimeoutSec),
548                               this, &SyncSetupHandler::DisplayTimeout);
549
550   web_ui()->CallJavascriptFunction(
551       "SyncSetupOverlay.showSyncSetupPage", page, args);
552 }
553
554 // TODO(kochi): Handle error conditions other than timeout.
555 // http://crbug.com/128692
556 void SyncSetupHandler::DisplayTimeout() {
557   // Stop a timer to handle timeout in waiting for checking network connection.
558   backend_start_timer_.reset();
559
560   // Do not listen to sync startup events.
561   sync_startup_tracker_.reset();
562
563   base::StringValue page("timeout");
564   base::DictionaryValue args;
565   web_ui()->CallJavascriptFunction(
566       "SyncSetupOverlay.showSyncSetupPage", page, args);
567 }
568
569 void SyncSetupHandler::OnDidClosePage(const base::ListValue* args) {
570   CloseSyncSetup();
571 }
572
573 void SyncSetupHandler::SyncStartupFailed() {
574   // Stop a timer to handle timeout in waiting for checking network connection.
575   backend_start_timer_.reset();
576
577   // Just close the sync overlay (the idea is that the base settings page will
578   // display the current error.)
579   CloseUI();
580 }
581
582 void SyncSetupHandler::SyncStartupCompleted() {
583   ProfileSyncService* service = GetSyncService();
584   DCHECK(service->sync_initialized());
585
586   // Stop a timer to handle timeout in waiting for checking network connection.
587   backend_start_timer_.reset();
588
589   DisplayConfigureSync(true, false);
590 }
591
592 Profile* SyncSetupHandler::GetProfile() const {
593   return Profile::FromWebUI(web_ui());
594 }
595
596 ProfileSyncService* SyncSetupHandler::GetSyncService() const {
597   Profile* profile = GetProfile();
598   return profile->IsSyncAccessible() ?
599       ProfileSyncServiceFactory::GetForProfile(GetProfile()) : NULL;
600 }
601
602 void SyncSetupHandler::HandleConfigure(const base::ListValue* args) {
603   DCHECK(!sync_startup_tracker_);
604   std::string json;
605   if (!args->GetString(0, &json)) {
606     NOTREACHED() << "Could not read JSON argument";
607     return;
608   }
609   if (json.empty()) {
610     NOTREACHED();
611     return;
612   }
613
614   SyncConfigInfo configuration;
615   if (!GetConfiguration(json, &configuration)) {
616     // The page sent us something that we didn't understand.
617     // This probably indicates a programming error.
618     NOTREACHED();
619     return;
620   }
621
622   // Start configuring the ProfileSyncService using the configuration passed
623   // to us from the JS layer.
624   ProfileSyncService* service = GetSyncService();
625
626   // If the sync engine has shutdown for some reason, just close the sync
627   // dialog.
628   if (!service || !service->sync_initialized()) {
629     CloseUI();
630     return;
631   }
632
633   // Disable sync, but remain signed in if the user selected "Sync nothing" in
634   // the advanced settings dialog. Note: In order to disable sync across
635   // restarts on Chrome OS, we must call OnStopSyncingPermanently(), which
636   // suppresses sync startup in addition to disabling it.
637   if (configuration.sync_nothing) {
638     ProfileSyncService::SyncEvent(
639         ProfileSyncService::STOP_FROM_ADVANCED_DIALOG);
640     CloseUI();
641     service->OnStopSyncingPermanently();
642     service->SetSetupInProgress(false);
643     return;
644   }
645
646   // Note: Data encryption will not occur until configuration is complete
647   // (when the PSS receives its CONFIGURE_DONE notification from the sync
648   // backend), so the user still has a chance to cancel out of the operation
649   // if (for example) some kind of passphrase error is encountered.
650   if (configuration.encrypt_all)
651     service->EnableEncryptEverything();
652
653   bool passphrase_failed = false;
654   if (!configuration.passphrase.empty()) {
655     // We call IsPassphraseRequired() here (instead of
656     // IsPassphraseRequiredForDecryption()) because the user may try to enter
657     // a passphrase even though no encrypted data types are enabled.
658     if (service->IsPassphraseRequired()) {
659       // If we have pending keys, try to decrypt them with the provided
660       // passphrase. We track if this succeeds or fails because a failed
661       // decryption should result in an error even if there aren't any encrypted
662       // data types.
663       passphrase_failed =
664           !service->SetDecryptionPassphrase(configuration.passphrase);
665     } else {
666       // OK, the user sent us a passphrase, but we don't have pending keys. So
667       // it either means that the pending keys were resolved somehow since the
668       // time the UI was displayed (re-encryption, pending passphrase change,
669       // etc) or the user wants to re-encrypt.
670       if (!configuration.passphrase_is_gaia &&
671           !service->IsUsingSecondaryPassphrase()) {
672         // User passed us a secondary passphrase, and the data is encrypted
673         // with a GAIA passphrase so they must want to encrypt.
674         service->SetEncryptionPassphrase(configuration.passphrase,
675                                          ProfileSyncService::EXPLICIT);
676       }
677     }
678   }
679
680   bool user_was_prompted_for_passphrase =
681       service->IsPassphraseRequiredForDecryption();
682   service->OnUserChoseDatatypes(configuration.sync_everything,
683                                 configuration.data_types);
684
685   // Need to call IsPassphraseRequiredForDecryption() *after* calling
686   // OnUserChoseDatatypes() because the user may have just disabled the
687   // encrypted datatypes (in which case we just want to exit, not prompt the
688   // user for a passphrase).
689   if (passphrase_failed || service->IsPassphraseRequiredForDecryption()) {
690     // We need a passphrase, or the user's attempt to set a passphrase failed -
691     // prompt them again. This covers a few subtle cases:
692     // 1) The user enters an incorrect passphrase *and* disabled the encrypted
693     //    data types. In that case we want to notify the user that the
694     //    passphrase was incorrect even though there are no longer any encrypted
695     //    types enabled (IsPassphraseRequiredForDecryption() == false).
696     // 2) The user doesn't enter any passphrase. In this case, we won't call
697     //    SetDecryptionPassphrase() (passphrase_failed == false), but we still
698     //    want to display an error message to let the user know that their
699     //    blank passphrase entry is not acceptable.
700     // 3) The user just enabled an encrypted data type - in this case we don't
701     //    want to display an "invalid passphrase" error, since it's the first
702     //    time the user is seeing the prompt.
703     DisplayConfigureSync(
704         true, passphrase_failed || user_was_prompted_for_passphrase);
705   } else {
706     // No passphrase is required from the user so mark the configuration as
707     // complete and close the sync setup overlay.
708     ConfigureSyncDone();
709   }
710
711   ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE);
712   if (configuration.encrypt_all)
713     ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT);
714   if (configuration.passphrase_is_gaia && !configuration.passphrase.empty())
715     ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE);
716   if (!configuration.sync_everything)
717     ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE);
718 }
719
720 void SyncSetupHandler::HandleShowSetupUI(const base::ListValue* args) {
721   ProfileSyncService* service = GetSyncService();
722   DCHECK(service);
723
724   SigninManagerBase* signin =
725       SigninManagerFactory::GetForProfile(GetProfile());
726   if (signin->GetAuthenticatedUsername().empty()) {
727     // For web-based signin, the signin page is not displayed in an overlay
728     // on the settings page. So if we get here, it must be due to the user
729     // cancelling signin (by reloading the sync settings page during initial
730     // signin) or by directly navigating to settings/syncSetup
731     // (http://crbug.com/229836). So just exit and go back to the settings page.
732     DLOG(WARNING) << "Cannot display sync setup UI when not signed in";
733     CloseUI();
734     return;
735   }
736
737   // If a setup wizard is already present, but not on this page, close the
738   // blank setup overlay on this page by showing the "done" page. This can
739   // happen if the user navigates to chrome://settings/syncSetup in more than
740   // one tab. See crbug.com/261566.
741   // Note: The following block will transfer focus to the existing wizard.
742   if (IsExistingWizardPresent() && !IsActiveLogin()) {
743     CloseUI();
744   }
745
746   // If a setup wizard is present on this page or another, bring it to focus.
747   // Otherwise, display a new one on this page.
748   if (!FocusExistingWizardIfPresent())
749     OpenSyncSetup();
750 }
751
752 #if defined(OS_CHROMEOS)
753 // On ChromeOS, we need to sign out the user session to fix an auth error, so
754 // the user goes through the real signin flow to generate a new auth token.
755 void SyncSetupHandler::HandleDoSignOutOnAuthError(const base::ListValue* args) {
756   DVLOG(1) << "Signing out the user to fix a sync error.";
757   chrome::AttemptUserExit();
758 }
759 #endif
760
761 #if !defined(OS_CHROMEOS)
762 void SyncSetupHandler::HandleStartSignin(const base::ListValue* args) {
763   // Should only be called if the user is not already signed in.
764   DCHECK(SigninManagerFactory::GetForProfile(GetProfile())->
765       GetAuthenticatedUsername().empty());
766   OpenSyncSetup();
767 }
768
769 void SyncSetupHandler::HandleStopSyncing(const base::ListValue* args) {
770   if (GetSyncService())
771     ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS);
772   SigninManagerFactory::GetForProfile(GetProfile())->SignOut();
773 }
774 #endif
775
776 void SyncSetupHandler::HandleCloseTimeout(const base::ListValue* args) {
777   CloseSyncSetup();
778 }
779
780 void SyncSetupHandler::CloseSyncSetup() {
781   // Stop a timer to handle timeout in waiting for checking network connection.
782   backend_start_timer_.reset();
783
784   // Clear the sync startup tracker, since the setup wizard is being closed.
785   sync_startup_tracker_.reset();
786
787   ProfileSyncService* sync_service = GetSyncService();
788   if (IsActiveLogin()) {
789     // Don't log a cancel event if the sync setup dialog is being
790     // automatically closed due to an auth error.
791     if (!sync_service || (!sync_service->HasSyncSetupCompleted() &&
792         sync_service->GetAuthError().state() == GoogleServiceAuthError::NONE)) {
793       if (configuring_sync_) {
794         ProfileSyncService::SyncEvent(
795             ProfileSyncService::CANCEL_DURING_CONFIGURE);
796       }
797
798       // If the user clicked "Cancel" while setting up sync, disable sync
799       // because we don't want the sync backend to remain in the initialized
800       // state. Note: In order to disable sync across restarts on Chrome OS, we
801       // must call OnStopSyncingPermanently(), which suppresses sync startup in
802       // addition to disabling it.
803       if (sync_service) {
804         DVLOG(1) << "Sync setup aborted by user action";
805         sync_service->OnStopSyncingPermanently();
806 #if !defined(OS_CHROMEOS)
807         // Sign out the user on desktop Chrome if they click cancel during
808         // initial setup.
809         // TODO(rsimha): Revisit this for M30. See http://crbug.com/252049.
810         if (sync_service->FirstSetupInProgress())
811           SigninManagerFactory::GetForProfile(GetProfile())->SignOut();
812 #endif
813       }
814     }
815
816     GetLoginUIService()->LoginUIClosed(this);
817   }
818
819   // Alert the sync service anytime the sync setup dialog is closed. This can
820   // happen due to the user clicking the OK or Cancel button, or due to the
821   // dialog being closed by virtue of sync being disabled in the background.
822   if (sync_service)
823     sync_service->SetSetupInProgress(false);
824
825   configuring_sync_ = false;
826 }
827
828 void SyncSetupHandler::OpenSyncSetup() {
829   if (!PrepareSyncSetup())
830     return;
831
832   // There are several different UI flows that can bring the user here:
833   // 1) Signin promo.
834   // 2) Normal signin through settings page (GetAuthenticatedUsername() is
835   //    empty).
836   // 3) Previously working credentials have expired.
837   // 4) User is signed in, but has stopped sync via the google dashboard, and
838   //    signout is prohibited by policy so we need to force a re-auth.
839   // 5) User clicks [Advanced Settings] button on options page while already
840   //    logged in.
841   // 6) One-click signin (credentials are already available, so should display
842   //    sync configure UI, not login UI).
843   // 7) User re-enables sync after disabling it via advanced settings.
844 #if !defined(OS_CHROMEOS)
845   SigninManagerBase* signin =
846       SigninManagerFactory::GetForProfile(GetProfile());
847
848   if (signin->GetAuthenticatedUsername().empty() ||
849       SigninGlobalError::GetForProfile(GetProfile())->HasMenuItem()) {
850     // User is not logged in (cases 1-2), or login has been specially requested
851     // because previously working credentials have expired (case 3). Close sync
852     // setup including any visible overlays, and display the gaia auth page.
853     // Control will be returned to the sync settings page once auth is complete.
854     CloseUI();
855     DisplayGaiaLogin();
856     return;
857   }
858 #endif
859   if (!GetSyncService()) {
860     // This can happen if the user directly navigates to /settings/syncSetup.
861     DLOG(WARNING) << "Cannot display sync UI when sync is disabled";
862     CloseUI();
863     return;
864   }
865
866   // User is already logged in. They must have brought up the config wizard
867   // via the "Advanced..." button or through One-Click signin (cases 4-6), or
868   // they are re-enabling sync after having disabled it (case 7).
869   DisplayConfigureSync(true, false);
870 }
871
872 void SyncSetupHandler::OpenConfigureSync() {
873   if (!PrepareSyncSetup())
874     return;
875
876   DisplayConfigureSync(true, false);
877 }
878
879 void SyncSetupHandler::FocusUI() {
880   DCHECK(IsActiveLogin());
881   WebContents* web_contents = web_ui()->GetWebContents();
882   web_contents->GetDelegate()->ActivateContents(web_contents);
883 }
884
885 void SyncSetupHandler::CloseUI() {
886   CloseSyncSetup();
887   base::StringValue page("done");
888   web_ui()->CallJavascriptFunction(
889       "SyncSetupOverlay.showSyncSetupPage", page);
890 }
891
892 bool SyncSetupHandler::IsExistingWizardPresent() {
893   LoginUIService* service = GetLoginUIService();
894   DCHECK(service);
895   return service->current_login_ui() != NULL;
896 }
897
898 bool SyncSetupHandler::FocusExistingWizardIfPresent() {
899   if (!IsExistingWizardPresent())
900     return false;
901
902   LoginUIService* service = GetLoginUIService();
903   DCHECK(service);
904   service->current_login_ui()->FocusUI();
905   return true;
906 }
907
908 LoginUIService* SyncSetupHandler::GetLoginUIService() const {
909   return LoginUIServiceFactory::GetForProfile(GetProfile());
910 }