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
26 typedef GoogleServiceAuthError AuthError;
27
28 namespace sync_ui_util {
29
30 namespace {
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.
GetStatusLabelsForAuthError(const AuthError & auth_error,ProfileSyncService * service,string16 * status_label,string16 * link_label)35 void 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.
GetSyncedStateStatusLabel(ProfileSyncService * service)72 string16 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.
GetStatusInfo(ProfileSyncService * service,string16 * status_label,string16 * link_label)90 MessageType 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.
GetStatusInfoForNewTabPage(ProfileSyncService * service,string16 * status_label,string16 * link_label)184 MessageType 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
GetStatusLabels(ProfileSyncService * service,string16 * status_label,string16 * link_label)221 MessageType 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
GetStatusLabelsForNewTabPage(ProfileSyncService * service,string16 * status_label,string16 * link_label)229 MessageType 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
GetStatus(ProfileSyncService * service)238 MessageType GetStatus(ProfileSyncService* service) {
239 return sync_ui_util::GetStatusInfo(service, NULL, NULL);
240 }
241
ShouldShowSyncErrorButton(ProfileSyncService * service)242 bool 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
GetSyncMenuLabel(ProfileSyncService * service)250 string16 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
OpenSyncMyBookmarksDialog(Profile * profile,Browser * browser,ProfileSyncService::SyncEventCodes code)261 void 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
AddBoolSyncDetail(ListValue * details,const std::string & stat_name,bool stat_value)284 void 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
AddIntSyncDetail(ListValue * details,const std::string & stat_name,int64 stat_value)293 void 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
ConstructTime(int64 time_in_int)301 string16 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
MakeSyncAuthErrorText(const GoogleServiceAuthError::State & state)310 std::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
ConstructAboutInformation(ProfileSyncService * service,DictionaryValue * strings)327 void 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