chromium/chrome/browser/ash/locale/locale_change_guard.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/locale/locale_change_guard.h"

#include <algorithm>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ash/base/locale_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/common/pref_names.h"
#include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/locale_util.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {

namespace {

using ::base::UserMetricsAction;
using ::content::WebContents;

// This is the list of languages that do not require user notification when
// locale is switched automatically between regions within the same language.
//
// New language in kAcceptLanguageList should be added either here or to
// to the exception list in unit test.
const char* const kSkipShowNotificationLanguages[4] = {"en", "de", "fr", "it"};

}  // anonymous namespace

LocaleChangeGuard::LocaleChangeGuard(Profile* profile) : profile_(profile) {
  DCHECK(profile_);
  DeviceSettingsService::Get()->AddObserver(this);
}

LocaleChangeGuard::~LocaleChangeGuard() {
  if (DeviceSettingsService::IsInitialized())
    DeviceSettingsService::Get()->RemoveObserver(this);
}

void LocaleChangeGuard::OnLogin() {
  if (session_manager::SessionManager::Get()->IsSessionStarted()) {
    Check();
  } else {
    if (session_observation_.IsObserving()) {
      DCHECK(session_observation_.IsObservingSource(
          session_manager::SessionManager::Get()));
      return;
    }
    session_observation_.Observe(session_manager::SessionManager::Get());
  }
}

void LocaleChangeGuard::RevertLocaleChange() {
  if (from_locale_.empty() || to_locale_.empty()) {
    NOTREACHED_IN_MIGRATION();
    return;
  }
  if (reverted_)
    return;
  reverted_ = true;
  base::RecordAction(UserMetricsAction("LanguageChange_Revert"));
  profile_->ChangeAppLocale(from_locale_,
                            Profile::APP_LOCALE_CHANGED_VIA_REVERT);
  chrome::AttemptUserExit();
}

void LocaleChangeGuard::OnUserSessionStarted(bool is_primary_user) {
  session_observation_.Reset();
  Check();
}

void LocaleChangeGuard::OwnershipStatusChanged() {
  if (!DeviceSettingsService::Get()->HasPrivateOwnerKey())
    return;
  PrefService* local_state = g_browser_process->local_state();
  if (!local_state)
    return;
  PrefService* prefs = profile_->GetPrefs();
  DCHECK(prefs);
  std::string owner_locale =
      prefs->GetString(language::prefs::kApplicationLocale);
  language::ConvertToActualUILocale(&owner_locale);
  if (!owner_locale.empty())
    local_state->SetString(prefs::kOwnerLocale, owner_locale);
}

void LocaleChangeGuard::Check() {
  std::string cur_locale = g_browser_process->GetApplicationLocale();
  if (cur_locale.empty()) {
    NOTREACHED_IN_MIGRATION();
    return;
  }

  PrefService* prefs = profile_->GetPrefs();
  if (prefs == nullptr) {
    NOTREACHED_IN_MIGRATION();
    return;
  }

  std::string to_locale = prefs->GetString(language::prefs::kApplicationLocale);
  // Ensure that synchronization does not change the locale to a value not
  // allowed by enterprise policy.
  if (!locale_util::IsAllowedUILanguage(to_locale, prefs)) {
    prefs->SetString(language::prefs::kApplicationLocale,
                     locale_util::GetAllowedFallbackUILanguage(prefs));
  }

  language::ConvertToActualUILocale(&to_locale);

  if (to_locale != cur_locale && locale_changed_during_login_) {
    LocaleUpdateController::Get()->OnLocaleChanged();
    return;
  }

  std::string from_locale = prefs->GetString(prefs::kApplicationLocaleBackup);

  if (!RequiresUserConfirmation(from_locale, to_locale)) {
    // If the locale changed during login (e.g. from the owner's locale), just
    // notify ash about the change, so system UI gets updated.
    // If the change also requires user confirmation, the UI will be updates as
    // part of `LocaleUpdateController::ConfirmLocaleChange`.
    if (locale_changed_during_login_)
      LocaleUpdateController::Get()->OnLocaleChanged();
    return;
  }

  // Showing notification.
  if (from_locale_ != from_locale || to_locale_ != to_locale) {
    // Falling back to showing message in current locale.
    LOG(ERROR) << "Showing locale change notification in current (not "
                  "previous) language";
    PrepareChangingLocale(from_locale, to_locale);
  }

  LocaleUpdateController::Get()->ConfirmLocaleChange(
      cur_locale, from_locale_, to_locale_,
      base::BindOnce(&LocaleChangeGuard::OnResult,
                     weak_ptr_factory_.GetWeakPtr()));
}

void LocaleChangeGuard::OnResult(LocaleNotificationResult result) {
  switch (result) {
    case LocaleNotificationResult::kAccept:
      AcceptLocaleChange();
      break;
    case LocaleNotificationResult::kRevert:
      RevertLocaleChange();
      break;
  }
}

void LocaleChangeGuard::AcceptLocaleChange() {
  if (from_locale_.empty() || to_locale_.empty()) {
    NOTREACHED_IN_MIGRATION();
    return;
  }

  // Check whether locale has been reverted or changed.
  // If not: mark current locale as accepted.
  if (reverted_)
    return;
  PrefService* prefs = profile_->GetPrefs();
  if (prefs == nullptr) {
    NOTREACHED_IN_MIGRATION();
    return;
  }
  if (prefs->GetString(language::prefs::kApplicationLocale) != to_locale_)
    return;
  base::RecordAction(UserMetricsAction("LanguageChange_Accept"));
  prefs->SetString(prefs::kApplicationLocaleBackup, to_locale_);
  prefs->SetString(prefs::kApplicationLocaleAccepted, to_locale_);
}

void LocaleChangeGuard::PrepareChangingLocale(const std::string& from_locale,
                                              const std::string& to_locale) {
  std::string cur_locale = g_browser_process->GetApplicationLocale();
  if (!from_locale.empty())
    from_locale_ = from_locale;
  if (!to_locale.empty())
    to_locale_ = to_locale;
}

bool LocaleChangeGuard::RequiresUserConfirmation(
    const std::string& from_locale,
    const std::string& to_locale) const {
  // No locale change was detected for the user.
  if (from_locale.empty() || from_locale == to_locale)
    return false;

  // The target locale is already accepted.
  if (profile_->GetPrefs()->GetString(prefs::kApplicationLocaleAccepted) ==
      to_locale) {
    return false;
  }

  return ShouldShowLocaleChangeNotification(from_locale, to_locale);
}

// static
bool LocaleChangeGuard::ShouldShowLocaleChangeNotification(
    const std::string& from_locale,
    const std::string& to_locale) {
  const std::string from_lang = l10n_util::GetLanguage(from_locale);
  const std::string to_lang = l10n_util::GetLanguage(to_locale);

  if (from_locale == to_locale)
    return false;

  if (from_lang != to_lang)
    return true;

  return !base::Contains(kSkipShowNotificationLanguages, from_lang);
}

// static
const char* const*
LocaleChangeGuard::GetSkipShowNotificationLanguagesForTesting() {
  return kSkipShowNotificationLanguages;
}

// static
size_t LocaleChangeGuard::GetSkipShowNotificationLanguagesSizeForTesting() {
  return std::size(kSkipShowNotificationLanguages);
}

}  // namespace ash