blob: 761e1dd3b99d3c35545112c5c86a64cafe341414 [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 <stdint.h>
#include <string>
#include "base/i18n/number_formatting.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_error_controller_factory.h"
#include "chrome/browser/signin/signin_ui_util.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/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/browser_sync/profile_sync_service.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/signin_error_controller.h"
#include "components/signin/core/browser/signin_manager_base.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/sync_prefs.h"
#include "components/sync/engine/cycle/sync_cycle_snapshot.h"
#include "components/sync/protocol/proto_enum_conversions.h"
#include "components/sync/protocol/sync_protocol_error.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(OS_CHROMEOS)
#include "components/signin/core/account_id/account_id.h"
#include "components/user_manager/user_manager.h"
#else
#include "chrome/browser/signin/signin_manager_factory.h"
#include "components/sync/driver/sync_error_controller.h"
#endif // defined(OS_CHROMEOS)
using browser_sync::ProfileSyncService;
using AuthError = GoogleServiceAuthError;
namespace sync_ui_util {
namespace {
bool IsChromeDashboardEnabled() {
const std::string group_name =
base::FieldTrialList::FindFullName("ChromeDashboard");
return group_name == "Enabled";
}
// 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.
base::string16 GetSyncedStateStatusLabel(ProfileSyncService* service,
const SigninManagerBase& signin,
StatusLabelStyle style,
bool sync_everything) {
if (!service || service->IsManaged()) {
// User is signed in, but sync is disabled.
return l10n_util::GetStringUTF16(IDS_SIGNED_IN_WITH_SYNC_DISABLED);
} else if (!service->IsSyncRequested()) {
// User is signed in, but sync has been stopped.
return l10n_util::GetStringUTF16(IDS_SIGNED_IN_WITH_SYNC_SUPPRESSED);
} else if (!service->IsSyncActive()) {
// User is not signed in, or sync is still initializing.
return base::string16();
}
// Message may also carry additional advice with an HTML link, if acceptable.
switch (style) {
case PLAIN_TEXT:
return l10n_util::GetStringUTF16(
sync_everything ? IDS_SYNC_ACCOUNT_SYNCING
: IDS_SYNC_ACCOUNT_SYNCING_CUSTOM_DATA_TYPES);
case WITH_HTML:
if (IsChromeDashboardEnabled()) {
return l10n_util::GetStringFUTF16(
IDS_SYNC_ACCOUNT_SYNCING_WITH_MANAGE_LINK_NEW,
base::ASCIIToUTF16(chrome::kSyncGoogleDashboardURL));
}
return l10n_util::GetStringFUTF16(
IDS_SYNC_ACCOUNT_SYNCING_WITH_MANAGE_LINK,
base::ASCIIToUTF16(chrome::kSyncGoogleDashboardURL));
default:
NOTREACHED();
return nullptr;
}
}
void GetStatusForActionableError(const syncer::SyncProtocolError& error,
base::string16* status_label,
base::string16* link_label,
ActionType* action_type) {
DCHECK(status_label);
DCHECK(link_label);
switch (error.action) {
case syncer::UPGRADE_CLIENT:
status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_UPGRADE_CLIENT));
link_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_UPGRADE_CLIENT_LINK_LABEL));
*action_type = UPGRADE_CLIENT;
break;
case syncer::ENABLE_SYNC_ON_ACCOUNT:
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_STATUS_ENABLE_SYNC_ON_ACCOUNT));
break;
default:
status_label->clear();
break;
}
}
void GetStatusForUnrecoverableError(Profile* profile,
ProfileSyncService* service,
base::string16* status_label,
base::string16* link_label,
ActionType* action_type) {
// Unrecoverable error is sometimes accompanied by actionable error.
// If status message is set display that message, otherwise show generic
// unrecoverable error message.
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
GetStatusForActionableError(status.sync_protocol_error, status_label,
link_label, action_type);
if (status_label->empty()) {
*action_type = REAUTHENTICATE;
link_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_LINK_LABEL));
#if !defined(OS_CHROMEOS)
status_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR));
// The message for managed accounts is the same as that of the cros.
if (SigninManagerFactory::GetForProfile(profile)->IsSignoutProhibited()) {
status_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR_NEEDS_SIGNOUT));
}
#else
status_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR_NEEDS_SIGNOUT));
#endif
}
}
// Depending on the authentication state, returns labels to be used to display
// information about the sync status.
void GetStatusForAuthError(Profile* profile,
const SigninManagerBase& signin_manager,
base::string16* status_label,
base::string16* link_label,
ActionType* action_type) {
DCHECK(status_label);
DCHECK(link_label);
const GoogleServiceAuthError::State state =
SigninErrorControllerFactory::GetForProfile(profile)->
auth_error().state();
switch (state) {
case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_SERVICE_UNAVAILABLE));
break;
case GoogleServiceAuthError::CONNECTION_FAILED:
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_SERVER_IS_UNREACHABLE));
// 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.
break;
case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
case GoogleServiceAuthError::SERVICE_ERROR:
case GoogleServiceAuthError::ACCOUNT_DELETED:
case GoogleServiceAuthError::ACCOUNT_DISABLED:
default:
status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_ERROR));
link_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_LINK_LABEL));
*action_type = REAUTHENTICATE;
break;
}
}
// 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(Profile* profile,
ProfileSyncService* service,
const SigninManagerBase& signin,
StatusLabelStyle style,
base::string16* status_label,
base::string16* link_label,
ActionType* action_type) {
DCHECK_EQ(status_label == nullptr, link_label == nullptr);
MessageType result_type(SYNCED);
if (!signin.IsAuthenticated())
return PRE_SYNCED;
if (!service || service->IsManaged() || service->IsFirstSetupComplete() ||
!service->IsSyncRequested()) {
// The order or priority is going to be: 1. Unrecoverable errors.
// 2. Auth errors. 3. Protocol errors. 4. Passphrase errors.
if (service && service->HasUnrecoverableError()) {
if (status_label && link_label) {
GetStatusForUnrecoverableError(profile, service, status_label,
link_label, action_type);
}
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;
}
PrefService* pref_service = profile->GetPrefs();
syncer::SyncPrefs sync_prefs(pref_service);
bool sync_everything = sync_prefs.HasKeepEverythingSynced();
// Check for sync errors if the sync service is enabled.
if (service) {
// Since there is no auth in progress, check for an auth error first.
AuthError auth_error =
SigninErrorControllerFactory::GetForProfile(profile)->auth_error();
if (auth_error.state() != AuthError::NONE) {
if (status_label && link_label) {
GetStatusForAuthError(profile, signin, status_label, link_label,
action_type);
}
return SYNC_ERROR;
}
// We don't have an auth error. Check for an actionable error.
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
if (status_label && link_label) {
GetStatusForActionableError(status.sync_protocol_error, status_label,
link_label, action_type);
if (!status_label->empty())
return SYNC_ERROR;
}
// Check for a passphrase error.
if (service->IsPassphraseRequired() &&
service->IsPassphraseRequiredForDecryption()) {
if (status_label && link_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_STATUS_NEEDS_PASSWORD));
link_label->assign(
l10n_util::GetStringUTF16(
IDS_SYNC_STATUS_NEEDS_PASSWORD_LINK_LABEL));
*action_type = ENTER_PASSPHRASE;
}
return SYNC_ERROR;
}
// Check to see if sync has been disabled via the dasboard and needs to be
// set up once again.
if (!service->IsSyncRequested() &&
status.sync_protocol_error.error_type == syncer::NOT_MY_BIRTHDAY) {
if (status_label) {
status_label->assign(GetSyncedStateStatusLabel(service, signin, style,
sync_everything));
}
return PRE_SYNCED;
}
}
// There is no error. Display "Last synced..." message.
if (status_label) {
status_label->assign(
GetSyncedStateStatusLabel(service, signin, style, sync_everything));
}
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.
if (service->IsFirstSetupInProgress()) {
result_type = PRE_SYNCED;
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
AuthError auth_error =
SigninErrorControllerFactory::GetForProfile(profile)->auth_error();
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 && link_label) {
GetStatusForAuthError(profile, signin, status_label, link_label,
action_type);
}
result_type = SYNC_ERROR;
}
} else if (service->HasUnrecoverableError()) {
result_type = SYNC_ERROR;
if (status_label && link_label) {
GetStatusForUnrecoverableError(profile, service, status_label,
link_label, action_type);
}
} else if (signin.IsAuthenticated()) {
if (service->IsSyncConfirmationNeeded()) {
if (status_label && link_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_SETTINGS_NOT_CONFIRMED));
link_label->assign(l10n_util::GetStringUTF16(
IDS_SYNC_ERROR_USER_MENU_CONFIRM_SYNC_SETTINGS_BUTTON));
}
*action_type = CONFIRM_SYNC_SETTINGS;
result_type = SYNC_ERROR;
} else {
// The user is signed in, but sync has been stopped.
result_type = PRE_SYNCED;
if (status_label) {
status_label->assign(
l10n_util::GetStringUTF16(IDS_SIGNED_IN_WITH_SYNC_SUPPRESSED));
}
}
}
}
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(Profile* profile,
ProfileSyncService* service,
const SigninManagerBase& signin,
base::string16* status_label,
base::string16* link_label) {
DCHECK(status_label);
DCHECK(link_label);
if (service->IsFirstSetupComplete() && 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(base::string16());
link_label->assign(
l10n_util::GetStringUTF16(IDS_SYNC_CONFIGURE_ENCRYPTION));
}
return SYNC_ERROR;
}
}
// Fallback to default.
ActionType action_type = NO_ACTION;
return GetStatusInfo(profile, service, signin, WITH_HTML, status_label,
link_label, &action_type);
}
} // namespace
MessageType GetStatusLabels(Profile* profile,
ProfileSyncService* service,
const SigninManagerBase& signin,
StatusLabelStyle style,
base::string16* status_label,
base::string16* link_label,
ActionType* action_type) {
DCHECK(status_label);
DCHECK(link_label);
return sync_ui_util::GetStatusInfo(profile, service, signin, style,
status_label, link_label, action_type);
}
MessageType GetStatusLabelsForNewTabPage(Profile* profile,
ProfileSyncService* service,
const SigninManagerBase& signin,
base::string16* status_label,
base::string16* link_label) {
DCHECK(status_label);
DCHECK(link_label);
return sync_ui_util::GetStatusInfoForNewTabPage(profile, service, signin,
status_label, link_label);
}
#if !defined(OS_CHROMEOS)
AvatarSyncErrorType GetMessagesForAvatarSyncError(
Profile* profile,
const SigninManagerBase& signin,
int* content_string_id,
int* button_string_id) {
ProfileSyncService* service =
ProfileSyncServiceFactory::GetForProfile(profile);
// The order or priority is going to be: 1. Unrecoverable errors.
// 2. Auth errors. 3. Protocol errors. 4. Passphrase errors.
if (service && service->HasUnrecoverableError()) {
// An unrecoverable error is sometimes accompanied by an actionable error.
// If an actionable error is not set to be UPGRADE_CLIENT, then show a
// generic unrecoverable error message.
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
if (status.sync_protocol_error.action != syncer::UPGRADE_CLIENT) {
// Display different messages and buttons for managed accounts.
if (SigninManagerFactory::GetForProfile(profile)->IsSignoutProhibited()) {
// For a managed user, the user is directed to the signout
// confirmation dialogue in the settings page.
*content_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNOUT_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNOUT_BUTTON;
return MANAGED_USER_UNRECOVERABLE_ERROR;
}
// For a non-managed user, we sign out on the user's behalf and prompt
// the user to sign in again.
*content_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_AGAIN_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_AGAIN_BUTTON;
return UNRECOVERABLE_ERROR;
}
}
// Check for an auth error.
SigninErrorController* signin_error_controller =
SigninErrorControllerFactory::GetForProfile(profile);
if (signin_error_controller && signin_error_controller->HasError()) {
if (profile->IsSupervised()) {
// For a supervised user, no direct action can be taken to resolve an
// auth token error.
*content_string_id = IDS_SYNC_ERROR_USER_MENU_SUPERVISED_SIGNIN_MESSAGE;
*button_string_id = 0;
return SUPERVISED_USER_AUTH_ERROR;
}
// For a non-supervised user, the user can reauth to resolve the signin
// error.
*content_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_BUTTON;
return AUTH_ERROR;
}
// Check for sync errors if the sync service is enabled.
if (service) {
// Check for an actionable UPGRADE_CLIENT error.
ProfileSyncService::Status status;
service->QueryDetailedSyncStatus(&status);
if (status.sync_protocol_error.action == syncer::UPGRADE_CLIENT) {
*content_string_id = IDS_SYNC_ERROR_USER_MENU_UPGRADE_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_UPGRADE_BUTTON;
return UPGRADE_CLIENT_ERROR;
}
// Check for a sync passphrase error.
syncer::SyncErrorController* sync_error_controller =
service->sync_error_controller();
if (sync_error_controller && sync_error_controller->HasError()) {
*content_string_id = IDS_SYNC_ERROR_USER_MENU_PASSPHRASE_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_PASSPHRASE_BUTTON;
return PASSPHRASE_ERROR;
}
// Check for a sync confirmation error.
if (signin.IsAuthenticated() && service->IsSyncConfirmationNeeded()) {
*content_string_id = IDS_SYNC_SETTINGS_NOT_CONFIRMED;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_CONFIRM_SYNC_SETTINGS_BUTTON;
return SETTINGS_UNCONFIRMED_ERROR;
}
}
// There is no error.
return NO_SYNC_ERROR;
}
#endif
MessageType GetStatus(Profile* profile,
ProfileSyncService* service,
const SigninManagerBase& signin) {
ActionType action_type = NO_ACTION;
return sync_ui_util::GetStatusInfo(profile, service, signin, WITH_HTML,
nullptr, nullptr, &action_type);
}
base::string16 ConstructTime(int64_t 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 base::string16();
return base::TimeFormatFriendlyDateAndTime(time);
}
} // namespace sync_ui_util