1// Copyright (c) 2011 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/sync/sync_ui_util.h"
6
7#include "base/command_line.h"
8#include "base/i18n/number_formatting.h"
9#include "base/i18n/time_formatting.h"
10#include "base/string_util.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/sync/profile_sync_service.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_window.h"
16#include "chrome/browser/ui/options/options_window.h"
17#include "chrome/common/chrome_switches.h"
18#include "chrome/common/net/gaia/google_service_auth_error.h"
19#include "chrome/common/url_constants.h"
20#include "grit/browser_resources.h"
21#include "grit/chromium_strings.h"
22#include "grit/generated_resources.h"
23#include "ui/base/l10n/l10n_util.h"
24#include "ui/base/resource/resource_bundle.h"
25
26typedef GoogleServiceAuthError AuthError;
27
28namespace sync_ui_util {
29
30namespace {
31
32// Given an authentication state, this helper function returns the appropriate
33// status message and, if necessary, the text that should appear in the
34// re-login link.
35void GetStatusLabelsForAuthError(const AuthError& auth_error,
36    ProfileSyncService* service, string16* status_label,
37    string16* link_label) {
38  if (link_label)
39    link_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_LINK_LABEL));
40  if (auth_error.state() == AuthError::INVALID_GAIA_CREDENTIALS ||
41      auth_error.state() == AuthError::ACCOUNT_DELETED ||
42      auth_error.state() == AuthError::ACCOUNT_DISABLED) {
43    // If the user name is empty then the first login failed, otherwise the
44    // credentials are out-of-date.
45    if (service->GetAuthenticatedUsername().empty())
46      status_label->assign(
47          l10n_util::GetStringUTF16(IDS_SYNC_INVALID_USER_CREDENTIALS));
48    else
49      status_label->assign(
50          l10n_util::GetStringUTF16(IDS_SYNC_LOGIN_INFO_OUT_OF_DATE));
51  } else if (auth_error.state() == AuthError::SERVICE_UNAVAILABLE) {
52    DCHECK(service->GetAuthenticatedUsername().empty());
53    status_label->assign(
54        l10n_util::GetStringUTF16(IDS_SYNC_SERVICE_UNAVAILABLE));
55  } else if (auth_error.state() == AuthError::CONNECTION_FAILED) {
56    // Note that there is little the user can do if the server is not
57    // reachable. Since attempting to re-connect is done automatically by
58    // the Syncer, we do not show the (re)login link.
59    status_label->assign(
60        l10n_util::GetStringFUTF16(IDS_SYNC_SERVER_IS_UNREACHABLE,
61                              l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
62    if (link_label)
63      link_label->clear();
64  } else {
65    status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_ERROR_SIGNING_IN));
66  }
67}
68
69// Returns the message that should be displayed when the user is authenticated
70// and can connect to the sync server. If the user hasn't yet authenticated, an
71// empty string is returned.
72string16 GetSyncedStateStatusLabel(ProfileSyncService* service) {
73  string16 label;
74  string16 user_name(service->GetAuthenticatedUsername());
75  if (user_name.empty())
76    return label;
77
78  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
79  return l10n_util::GetStringFUTF16(
80      browser_command_line.HasSwitch(switches::kMultiProfiles) ?
81          IDS_PROFILES_SYNCED_TO_USER_WITH_TIME :
82          IDS_SYNC_ACCOUNT_SYNCED_TO_USER_WITH_TIME,
83      user_name,
84      service->GetLastSyncedTimeString());
85}
86
87// TODO(akalin): Write unit tests for these three functions below.
88
89// status_label and link_label must either be both NULL or both non-NULL.
90MessageType GetStatusInfo(ProfileSyncService* service,
91                          string16* status_label,
92                          string16* link_label) {
93  DCHECK_EQ(status_label == NULL, link_label == NULL);
94
95  MessageType result_type(SYNCED);
96
97  if (!service) {
98    return PRE_SYNCED;
99  }
100
101  if (service->HasSyncSetupCompleted()) {
102    ProfileSyncService::Status status(service->QueryDetailedSyncStatus());
103    const AuthError& auth_error = service->GetAuthError();
104
105    // Either show auth error information with a link to re-login, auth in prog,
106    // or note that everything is OK with the last synced time.
107    if (status.authenticated && !service->observed_passphrase_required()) {
108      // Everything is peachy.
109      if (status_label) {
110        status_label->assign(GetSyncedStateStatusLabel(service));
111      }
112      DCHECK_EQ(auth_error.state(), AuthError::NONE);
113    } else if (service->UIShouldDepictAuthInProgress()) {
114      if (status_label) {
115        status_label->assign(
116          l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL));
117      }
118      result_type = PRE_SYNCED;
119    } else if (service->observed_passphrase_required()) {
120      if (service->passphrase_required_for_decryption()) {
121        // NOT first machine.
122        // Show a link ("needs attention"), but still indicate the
123        // current synced status.  Return SYNC_PROMO so that
124        // the configure link will still be shown.
125        if (status_label && link_label) {
126          status_label->assign(GetSyncedStateStatusLabel(service));
127          link_label->assign(
128              l10n_util::GetStringUTF16(IDS_SYNC_PASSWORD_SYNC_ATTENTION));
129        }
130        result_type = SYNC_PROMO;
131      } else {
132        // First machine.  Don't show promotion, just show everything
133        // normal.
134        if (status_label)
135          status_label->assign(GetSyncedStateStatusLabel(service));
136      }
137    } else if (auth_error.state() != AuthError::NONE) {
138      if (status_label && link_label) {
139        GetStatusLabelsForAuthError(auth_error, service,
140                                    status_label, link_label);
141      }
142      result_type = SYNC_ERROR;
143    }
144  } else {
145    // Either show auth error information with a link to re-login, auth in prog,
146    // or provide a link to continue with setup.
147    result_type = PRE_SYNCED;
148    if (service->SetupInProgress()) {
149      ProfileSyncService::Status status(service->QueryDetailedSyncStatus());
150      const AuthError& auth_error = service->GetAuthError();
151      if (status_label) {
152        status_label->assign(
153            l10n_util::GetStringUTF16(IDS_SYNC_NTP_SETUP_IN_PROGRESS));
154      }
155      if (service->UIShouldDepictAuthInProgress()) {
156        if (status_label) {
157          status_label->assign(
158              l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL));
159        }
160      } else if (auth_error.state() != AuthError::NONE) {
161        if (status_label) {
162          status_label->clear();
163          GetStatusLabelsForAuthError(auth_error, service, status_label, NULL);
164        }
165        result_type = SYNC_ERROR;
166      } else if (!status.authenticated) {
167        if (status_label) {
168          status_label->assign(
169              l10n_util::GetStringUTF16(IDS_SYNC_ACCOUNT_DETAILS_NOT_ENTERED));
170        }
171      }
172    } else if (service->unrecoverable_error_detected()) {
173      result_type = SYNC_ERROR;
174      if (status_label) {
175        status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_SETUP_ERROR));
176      }
177    }
178  }
179  return result_type;
180}
181
182// Returns the status info for use on the new tab page, where we want slightly
183// different information than in the settings panel.
184MessageType GetStatusInfoForNewTabPage(ProfileSyncService* service,
185                                       string16* status_label,
186                                       string16* link_label) {
187  DCHECK(status_label);
188  DCHECK(link_label);
189
190  if (service->HasSyncSetupCompleted() &&
191      service->observed_passphrase_required()) {
192    if (!service->passphrase_required_for_decryption()) {
193      // First machine migrating to passwords.  Show as a promotion.
194      if (status_label && link_label) {
195        status_label->assign(
196            l10n_util::GetStringFUTF16(
197                IDS_SYNC_NTP_PASSWORD_PROMO,
198                l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
199        link_label->assign(
200            l10n_util::GetStringUTF16(IDS_SYNC_NTP_PASSWORD_ENABLE));
201      }
202      return SYNC_PROMO;
203    } else {
204      // NOT first machine.
205      // Show a link and present as an error ("needs attention").
206      if (status_label && link_label) {
207        status_label->assign(string16());
208        link_label->assign(
209            l10n_util::GetStringUTF16(IDS_SYNC_CONFIGURE_ENCRYPTION));
210      }
211      return SYNC_ERROR;
212    }
213  }
214
215  // Fallback to default.
216  return GetStatusInfo(service, status_label, link_label);
217}
218
219}  // namespace
220
221MessageType GetStatusLabels(ProfileSyncService* service,
222                            string16* status_label,
223                            string16* link_label) {
224  DCHECK(status_label);
225  DCHECK(link_label);
226  return sync_ui_util::GetStatusInfo(service, status_label, link_label);
227}
228
229MessageType GetStatusLabelsForNewTabPage(ProfileSyncService* service,
230                                         string16* status_label,
231                                         string16* link_label) {
232  DCHECK(status_label);
233  DCHECK(link_label);
234  return sync_ui_util::GetStatusInfoForNewTabPage(
235      service, status_label, link_label);
236}
237
238MessageType GetStatus(ProfileSyncService* service) {
239  return sync_ui_util::GetStatusInfo(service, NULL, NULL);
240}
241
242bool ShouldShowSyncErrorButton(ProfileSyncService* service) {
243  return service &&
244         ((!service->IsManaged() &&
245           service->HasSyncSetupCompleted()) &&
246         (GetStatus(service) == sync_ui_util::SYNC_ERROR ||
247          service->observed_passphrase_required()));
248}
249
250string16 GetSyncMenuLabel(ProfileSyncService* service) {
251  MessageType type = GetStatus(service);
252
253  if (type == sync_ui_util::SYNCED)
254    return l10n_util::GetStringUTF16(IDS_SYNC_MENU_SYNCED_LABEL);
255  else if (type == sync_ui_util::SYNC_ERROR)
256    return l10n_util::GetStringUTF16(IDS_SYNC_MENU_SYNC_ERROR_LABEL);
257  else
258    return l10n_util::GetStringUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL);
259}
260
261void OpenSyncMyBookmarksDialog(Profile* profile,
262                               Browser* browser,
263                               ProfileSyncService::SyncEventCodes code) {
264  ProfileSyncService* service =
265    profile->GetOriginalProfile()->GetProfileSyncService();
266  if (!service || !service->IsSyncEnabled()) {
267    LOG(DFATAL) << "OpenSyncMyBookmarksDialog called with sync disabled";
268    return;
269  }
270
271  if (service->HasSyncSetupCompleted()) {
272    bool create_window = browser == NULL;
273    if (create_window)
274      browser = Browser::Create(profile);
275    browser->ShowOptionsTab(chrome::kPersonalOptionsSubPage);
276    if (create_window)
277      browser->window()->Show();
278  } else {
279    service->ShowLoginDialog(NULL);
280    ProfileSyncService::SyncEvent(code);  // UMA stats
281  }
282}
283
284void AddBoolSyncDetail(ListValue* details,
285                       const std::string& stat_name,
286                       bool stat_value) {
287  DictionaryValue* val = new DictionaryValue;
288  val->SetString("stat_name", stat_name);
289  val->SetBoolean("stat_value", stat_value);
290  details->Append(val);
291}
292
293void AddIntSyncDetail(ListValue* details, const std::string& stat_name,
294                      int64 stat_value) {
295  DictionaryValue* val = new DictionaryValue;
296  val->SetString("stat_name", stat_name);
297  val->SetString("stat_value", base::FormatNumber(stat_value));
298  details->Append(val);
299}
300
301string16 ConstructTime(int64 time_in_int) {
302  base::Time time = base::Time::FromInternalValue(time_in_int);
303
304  // If time is null the format function returns a time in 1969.
305  if (time.is_null())
306    return string16();
307  return base::TimeFormatFriendlyDateAndTime(time);
308}
309
310std::string MakeSyncAuthErrorText(
311    const GoogleServiceAuthError::State& state) {
312  switch (state) {
313    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
314    case GoogleServiceAuthError::ACCOUNT_DELETED:
315    case GoogleServiceAuthError::ACCOUNT_DISABLED:
316    case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
317      return "INVALID_GAIA_CREDENTIALS";
318    case GoogleServiceAuthError::USER_NOT_SIGNED_UP:
319      return "USER_NOT_SIGNED_UP";
320    case GoogleServiceAuthError::CONNECTION_FAILED:
321      return "CONNECTION_FAILED";
322    default:
323      return std::string();
324  }
325}
326
327void ConstructAboutInformation(ProfileSyncService* service,
328                               DictionaryValue* strings) {
329  CHECK(strings);
330  if (!service || !service->HasSyncSetupCompleted()) {
331    strings->SetString("summary", "SYNC DISABLED");
332  } else {
333    sync_api::SyncManager::Status full_status(
334        service->QueryDetailedSyncStatus());
335
336    strings->SetString("service_url", service->sync_service_url().spec());
337    strings->SetString("summary",
338                       ProfileSyncService::BuildSyncStatusSummaryText(
339                       full_status.summary));
340
341    strings->Set("authenticated",
342                 new FundamentalValue(full_status.authenticated));
343    strings->SetString("auth_problem",
344                       sync_ui_util::MakeSyncAuthErrorText(
345                       service->GetAuthError().state()));
346
347    strings->SetString("time_since_sync", service->GetLastSyncedTimeString());
348
349    ListValue* details = new ListValue();
350    strings->Set("details", details);
351    sync_ui_util::AddBoolSyncDetail(details,
352                                    "Server Up",
353                                    full_status.server_up);
354    sync_ui_util::AddBoolSyncDetail(details,
355                                    "Server Reachable",
356                                    full_status.server_reachable);
357    sync_ui_util::AddBoolSyncDetail(details,
358                                    "Server Broken",
359                                    full_status.server_broken);
360    sync_ui_util::AddBoolSyncDetail(details,
361                                    "Notifications Enabled",
362                                    full_status.notifications_enabled);
363    sync_ui_util::AddIntSyncDetail(details,
364                                   "Notifications Received",
365                                   full_status.notifications_received);
366    sync_ui_util::AddIntSyncDetail(details,
367                                   "Notifications Sent",
368                                   full_status.notifications_sent);
369    sync_ui_util::AddIntSyncDetail(details,
370                                   "Unsynced Count",
371                                   full_status.unsynced_count);
372    sync_ui_util::AddIntSyncDetail(details,
373                                   "Conflicting Count",
374                                   full_status.conflicting_count);
375    sync_ui_util::AddBoolSyncDetail(details, "Syncing", full_status.syncing);
376    sync_ui_util::AddBoolSyncDetail(details,
377                                    "Initial Sync Ended",
378                                    full_status.initial_sync_ended);
379    sync_ui_util::AddBoolSyncDetail(details,
380                                    "Syncer Stuck",
381                                    full_status.syncer_stuck);
382    sync_ui_util::AddIntSyncDetail(details,
383                                   "Updates Available",
384                                   full_status.updates_available);
385    sync_ui_util::AddIntSyncDetail(details,
386                                   "Updates Downloaded (All)",
387                                   full_status.updates_received);
388    sync_ui_util::AddIntSyncDetail(details,
389                                   "Updates Downloaded (Tombstones)",
390                                   full_status.tombstone_updates_received);
391    sync_ui_util::AddBoolSyncDetail(details,
392                                    "Disk Full",
393                                    full_status.disk_full);
394    sync_ui_util::AddIntSyncDetail(details,
395                                   "Max Consecutive Errors",
396                                   full_status.max_consecutive_errors);
397
398    if (service->unrecoverable_error_detected()) {
399      strings->Set("unrecoverable_error_detected", new FundamentalValue(true));
400      strings->SetString("unrecoverable_error_message",
401                         service->unrecoverable_error_message());
402      tracked_objects::Location loc(service->unrecoverable_error_location());
403      std::string location_str;
404      loc.Write(true, true, &location_str);
405      strings->SetString("unrecoverable_error_location", location_str);
406    } else if (!service->sync_initialized()) {
407      strings->SetString("summary", "Sync not yet initialized");
408    } else {
409      browser_sync::ModelSafeRoutingInfo routes;
410      service->GetModelSafeRoutingInfo(&routes);
411      ListValue* routing_info = new ListValue();
412      strings->Set("routing_info", routing_info);
413      browser_sync::ModelSafeRoutingInfo::const_iterator it = routes.begin();
414      for (; it != routes.end(); ++it) {
415        DictionaryValue* val = new DictionaryValue;
416        val->SetString("model_type", ModelTypeToString(it->first));
417        val->SetString("group", ModelSafeGroupToString(it->second));
418        routing_info->Append(val);
419      }
420
421      sync_ui_util::AddBoolSyncDetail(details,
422          "Autofill Migrated",
423          service->GetAutofillMigrationState() ==
424          syncable::MIGRATED);
425      syncable::AutofillMigrationDebugInfo info =
426          service->GetAutofillMigrationDebugInfo();
427
428      sync_ui_util::AddIntSyncDetail(details,
429                                     "Bookmarks created during migration",
430                                     info.bookmarks_added_during_migration);
431      sync_ui_util::AddIntSyncDetail(details,
432          "Autofill entries created during migration",
433          info.autofill_entries_added_during_migration);
434      sync_ui_util::AddIntSyncDetail(details,
435          "Autofill Profiles created during migration",
436          info.autofill_profile_added_during_migration);
437
438      DictionaryValue* val = new DictionaryValue;
439      val->SetString("stat_name", "Autofill Migration Time");
440      val->SetString("stat_value", ConstructTime(info.autofill_migration_time));
441      details->Append(val);
442    }
443  }
444}
445
446}  // namespace sync_ui_util
447