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. 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. 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. 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. 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 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 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 238 MessageType GetStatus(ProfileSyncService* service) { 239 return sync_ui_util::GetStatusInfo(service, NULL, NULL); 240 } 241 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 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 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 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 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 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 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 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