blob: b7f532233bb9ff4059b0735b2e4c896db0bcce65 [file] [log] [blame]
// 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/sync/sync_ui_util.h"
#include "base/i18n/number_formatting.h"
#include "base/i18n/time_formatting.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_manager.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "sync/internal_api/public/base/model_type.h"
#include "sync/internal_api/public/sessions/sync_session_snapshot.h"
#include "sync/protocol/proto_enum_conversions.h"
#include "sync/protocol/sync_protocol_error.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
typedef GoogleServiceAuthError AuthError;
namespace sync_ui_util {
namespace {
// Given an authentication state this helper function returns various labels
// that can be used to display information about the state.
void GetStatusLabelsForAuthError(const AuthError& auth_error,
const ProfileSyncService& service,
string16* status_label,
string16* link_label,
string16* global_error_menu_label,
string16* global_error_bubble_message,
string16* global_error_bubble_accept_label) {
string16 username = UTF8ToUTF16(service.profile()->GetPrefs()->GetString(
prefs::kGoogleServicesUsername));
string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
if (link_label)
link_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_LINK_LABEL));
switch (auth_error.state()) {
case AuthError::INVALID_GAIA_CREDENTIALS:
case AuthError::ACCOUNT_DELETED:
case AuthError::ACCOUNT_DISABLED:
// If the user name is empty then the first login failed, otherwise the
// credentials are out-of-date.
if (username.empty()) {
if (status_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_INVALID_USER_CREDENTIALS));
}
} else {
if (status_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_LOGIN_INFO_OUT_OF_DATE));
}
if (global_error_menu_label) {
global_error_menu_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM));
}
if (global_error_bubble_message) {
global_error_bubble_message->assign(l10n_util::GetStringFUTF16(
IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE, product_name));
}
if (global_error_bubble_accept_label) {
global_error_bubble_accept_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_ACCEPT));
}
}
break;
case AuthError::SERVICE_UNAVAILABLE:
if (status_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_SERVICE_UNAVAILABLE));
}
if (link_label)
link_label->clear();
if (global_error_menu_label) {
global_error_menu_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM));
}
if (global_error_bubble_message) {
global_error_bubble_message->assign(l10n_util::GetStringFUTF16(
IDS_SYNC_UNAVAILABLE_ERROR_BUBBLE_VIEW_MESSAGE, product_name));
}
if (global_error_bubble_accept_label) {
global_error_bubble_accept_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_UNAVAILABLE_ERROR_BUBBLE_VIEW_ACCEPT));
}
break;
case AuthError::CONNECTION_FAILED:
// Note that there is little the user can do if the server is not
// reachable. Since attempting to re-connect is done automatically by
// the Syncer, we do not show the (re)login link.
if (status_label) {
status_label->assign(
l10n_util::GetStringFUTF16(IDS_SYNC_SERVER_IS_UNREACHABLE,
product_name));
}
break;
default:
if (status_label) {
status_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_ERROR_SIGNING_IN));
}
if (global_error_menu_label) {
global_error_menu_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM));
}
if (global_error_bubble_message) {
global_error_bubble_message->assign(l10n_util::GetStringFUTF16(
IDS_SYNC_OTHER_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE, product_name));
}
if (global_error_bubble_accept_label) {
global_error_bubble_accept_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_ACCEPT));
}
break;
}
}
// Returns the message that should be displayed when the user is authenticated
// and can connect to the sync server. If the user hasn't yet authenticated, an
// empty string is returned.
string16 GetSyncedStateStatusLabel(ProfileSyncService* service,
StatusLabelStyle style) {
if (!service->sync_initialized())
return string16();
string16 user_name = UTF8ToUTF16(service->profile()->GetPrefs()->GetString(
prefs::kGoogleServicesUsername));
DCHECK(!user_name.empty());
// Message may also carry additional advice with an HTML link, if acceptable.
switch (style) {
case PLAIN_TEXT:
return l10n_util::GetStringFUTF16(
IDS_SYNC_ACCOUNT_SYNCING_TO_USER,
user_name);
case WITH_HTML:
return l10n_util::GetStringFUTF16(
IDS_SYNC_ACCOUNT_SYNCING_TO_USER_WITH_MANAGE_LINK,
user_name,
ASCIIToUTF16(chrome::kSyncGoogleDashboardURL));
default:
NOTREACHED();
return NULL;
}
}
void GetStatusForActionableError(
const syncer::SyncProtocolError& error,
string16* status_label) {
DCHECK(status_label);
switch (error.action) {
case syncer::STOP_AND_RESTART_SYNC:
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_STOP_AND_RESTART_SYNC));
break;
case syncer::UPGRADE_CLIENT:
status_label->assign(
l10n_util::GetStringFUTF16(IDS_SYNC_UPGRADE_CLIENT,
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
break;
case syncer::ENABLE_SYNC_ON_ACCOUNT:
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_ENABLE_SYNC_ON_ACCOUNT));
break;
case syncer::CLEAR_USER_DATA_AND_RESYNC:
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_CLEAR_USER_DATA));
break;
default:
NOTREACHED();
}
}
// TODO(akalin): Write unit tests for these three functions below.
// status_label and link_label must either be both NULL or both non-NULL.
MessageType GetStatusInfo(ProfileSyncService* service,
const SigninManager& signin,
StatusLabelStyle style,
string16* status_label,
string16* link_label) {
DCHECK_EQ(status_label == NULL, link_label == NULL);
MessageType result_type(SYNCED);
if (!service) {
return PRE_SYNCED;
}
if (service->HasSyncSetupCompleted()) {
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
const AuthError& auth_error = service->GetAuthError();
// The order or priority is going to be: 1. Unrecoverable errors.
// 2. Auth errors. 3. Protocol errors. 4. Passphrase errors.
if (service->HasUnrecoverableError()) {
if (status_label) {
status_label->assign(l10n_util::GetStringFUTF16(
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR,
l10n_util::GetStringUTF16(IDS_SYNC_UNRECOVERABLE_ERROR_HELP_URL)));
}
return SYNC_ERROR;
}
// For auth errors first check if an auth is in progress.
if (signin.AuthInProgress()) {
if (status_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL));
}
return PRE_SYNCED;
}
// No auth in progress check for an auth error.
if (auth_error.state() != AuthError::NONE) {
if (status_label && link_label) {
GetStatusLabelsForAuthError(auth_error, *service,
status_label, link_label, NULL, NULL, NULL);
}
return SYNC_ERROR;
}
// We dont have an auth error. Check for protocol error.
if (ShouldShowActionOnUI(status.sync_protocol_error)) {
if (status_label) {
GetStatusForActionableError(status.sync_protocol_error,
status_label);
}
return SYNC_ERROR;
}
// Now finally passphrase error.
if (service->IsPassphraseRequired()) {
if (service->IsPassphraseRequiredForDecryption()) {
// TODO(lipalani) : Ask tim if this is still needed.
// NOT first machine.
// Show a link ("needs attention"), but still indicate the
// current synced status. Return SYNC_PROMO so that
// the configure link will still be shown.
if (status_label && link_label) {
status_label->assign(GetSyncedStateStatusLabel(service, style));
link_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_PASSWORD_SYNC_ATTENTION));
}
return SYNC_PROMO;
}
}
// There is no error. Display "Last synced..." message.
if (status_label)
status_label->assign(GetSyncedStateStatusLabel(service, style));
return SYNCED;
} else {
// Either show auth error information with a link to re-login, auth in prog,
// or provide a link to continue with setup.
result_type = PRE_SYNCED;
if (service->FirstSetupInProgress()) {
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
const AuthError& auth_error = service->GetAuthError();
if (status_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_NTP_SETUP_IN_PROGRESS));
}
if (signin.AuthInProgress()) {
if (status_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL));
}
} else if (auth_error.state() != AuthError::NONE &&
auth_error.state() != AuthError::TWO_FACTOR) {
if (status_label) {
status_label->clear();
GetStatusLabelsForAuthError(auth_error, *service, status_label, NULL,
NULL, NULL, NULL);
}
result_type = SYNC_ERROR;
}
} else if (service->HasUnrecoverableError()) {
result_type = SYNC_ERROR;
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
if (ShouldShowActionOnUI(status.sync_protocol_error)) {
if (status_label) {
GetStatusForActionableError(status.sync_protocol_error,
status_label);
}
} else if (status_label) {
status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_SETUP_ERROR));
}
}
}
return result_type;
}
// Returns the status info for use on the new tab page, where we want slightly
// different information than in the settings panel.
MessageType GetStatusInfoForNewTabPage(ProfileSyncService* service,
const SigninManager& signin,
string16* status_label,
string16* link_label) {
DCHECK(status_label);
DCHECK(link_label);
if (service->HasSyncSetupCompleted() &&
service->IsPassphraseRequired()) {
if (service->passphrase_required_reason() == syncer::REASON_ENCRYPTION) {
// First machine migrating to passwords. Show as a promotion.
if (status_label && link_label) {
status_label->assign(
l10n_util::GetStringFUTF16(
IDS_SYNC_NTP_PASSWORD_PROMO,
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
link_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_NTP_PASSWORD_ENABLE));
}
return SYNC_PROMO;
} else {
// NOT first machine.
// Show a link and present as an error ("needs attention").
if (status_label && link_label) {
status_label->assign(string16());
link_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_CONFIGURE_ENCRYPTION));
}
return SYNC_ERROR;
}
}
// Fallback to default.
return GetStatusInfo(service, signin, WITH_HTML, status_label, link_label);
}
} // namespace
MessageType GetStatusLabels(ProfileSyncService* service,
const SigninManager& signin,
StatusLabelStyle style,
string16* status_label,
string16* link_label) {
DCHECK(status_label);
DCHECK(link_label);
return sync_ui_util::GetStatusInfo(
service, signin, style, status_label, link_label);
}
MessageType GetStatusLabelsForNewTabPage(ProfileSyncService* service,
const SigninManager& signin,
string16* status_label,
string16* link_label) {
DCHECK(status_label);
DCHECK(link_label);
return sync_ui_util::GetStatusInfoForNewTabPage(
service, signin, status_label, link_label);
}
void GetStatusLabelsForSyncGlobalError(ProfileSyncService* service,
const SigninManager& signin,
string16* menu_label,
string16* bubble_message,
string16* bubble_accept_label) {
DCHECK(menu_label);
DCHECK(bubble_message);
DCHECK(bubble_accept_label);
*menu_label = string16();
*bubble_message = string16();
*bubble_accept_label = string16();
if (!service->HasSyncSetupCompleted())
return;
MessageType status = GetStatus(service, signin);
if (status == SYNC_ERROR) {
const AuthError& auth_error = service->GetAuthError();
if (auth_error.state() != AuthError::NONE) {
GetStatusLabelsForAuthError(auth_error, *service, NULL, NULL,
menu_label, bubble_message, bubble_accept_label);
// If we have an actionable auth error, display it.
if (!menu_label->empty())
return;
}
}
// No actionable auth error - display the passphrase error.
if (service->IsPassphraseRequired() &&
service->IsPassphraseRequiredForDecryption()) {
// This is not the first machine so ask user to enter passphrase.
*menu_label = l10n_util::GetStringUTF16(
IDS_SYNC_PASSPHRASE_ERROR_WRENCH_MENU_ITEM);
string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
*bubble_message = l10n_util::GetStringFUTF16(
IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE, product_name);
*bubble_accept_label = l10n_util::GetStringUTF16(
IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_ACCEPT);
return;
}
}
MessageType GetStatus(
ProfileSyncService* service, const SigninManager& signin) {
return sync_ui_util::GetStatusInfo(service, signin, WITH_HTML, NULL, NULL);
}
string16 GetSyncMenuLabel(
ProfileSyncService* service, const SigninManager& signin) {
MessageType type = GetStatus(service, signin);
if (type == sync_ui_util::SYNCED)
return l10n_util::GetStringUTF16(IDS_SYNC_MENU_SYNCED_LABEL);
else if (type == sync_ui_util::SYNC_ERROR)
return l10n_util::GetStringUTF16(IDS_SYNC_MENU_SYNC_ERROR_LABEL);
else
return l10n_util::GetStringUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL);
}
string16 ConstructTime(int64 time_in_int) {
base::Time time = base::Time::FromInternalValue(time_in_int);
// If time is null the format function returns a time in 1969.
if (time.is_null())
return string16();
return base::TimeFormatFriendlyDateAndTime(time);
}
std::string MakeSyncAuthErrorText(
const GoogleServiceAuthError::State& state) {
switch (state) {
case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
case GoogleServiceAuthError::ACCOUNT_DELETED:
case GoogleServiceAuthError::ACCOUNT_DISABLED:
case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
return "INVALID_GAIA_CREDENTIALS";
case GoogleServiceAuthError::USER_NOT_SIGNED_UP:
return "USER_NOT_SIGNED_UP";
case GoogleServiceAuthError::CONNECTION_FAILED:
return "CONNECTION_FAILED";
default:
return std::string();
}
}
} // namespace sync_ui_util