// Copyright 2021 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/ui/quick_answers/quick_answers_state_ash.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "chromeos/components/quick_answers/public/cpp/constants.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_prefs.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "chromeos/components/quick_answers/utils/quick_answers_metrics.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
using quick_answers::prefs::ConsentStatus;
using quick_answers::prefs::kQuickAnswersConsentStatus;
using quick_answers::prefs::kQuickAnswersDefinitionEnabled;
using quick_answers::prefs::kQuickAnswersEnabled;
using quick_answers::prefs::kQuickAnswersNoticeImpressionCount;
using quick_answers::prefs::kQuickAnswersTranslationEnabled;
using quick_answers::prefs::kQuickAnswersUnitConversionEnabled;
} // namespace
QuickAnswersStateAsh::QuickAnswersStateAsh() {
shell_observation_.Observe(ash::Shell::Get());
auto* session_controller = ash::Shell::Get()->session_controller();
CHECK(session_controller);
session_observation_.Observe(session_controller);
// Register pref changes if use session already started.
if (session_controller->IsActiveUserSessionStarted()) {
PrefService* prefs = session_controller->GetPrimaryUserPrefService();
DCHECK(prefs);
RegisterPrefChanges(prefs);
}
}
QuickAnswersStateAsh::~QuickAnswersStateAsh() = default;
void QuickAnswersStateAsh::OnFirstSessionStarted() {
PrefService* prefs =
ash::Shell::Get()->session_controller()->GetPrimaryUserPrefService();
RegisterPrefChanges(prefs);
}
void QuickAnswersStateAsh::OnChromeTerminating() {
session_observation_.Reset();
}
void QuickAnswersStateAsh::OnShellDestroying() {
session_observation_.Reset();
shell_observation_.Reset();
}
void QuickAnswersStateAsh::RegisterPrefChanges(PrefService* pref_service) {
pref_change_registrar_.reset();
if (!pref_service) {
return;
}
// Register preference changes.
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(pref_service);
pref_change_registrar_->Add(
kQuickAnswersEnabled,
base::BindRepeating(&QuickAnswersStateAsh::UpdateSettingsEnabled,
base::Unretained(this)));
pref_change_registrar_->Add(
kQuickAnswersConsentStatus,
base::BindRepeating(&QuickAnswersStateAsh::UpdateConsentStatus,
base::Unretained(this)));
pref_change_registrar_->Add(
kQuickAnswersDefinitionEnabled,
base::BindRepeating(&QuickAnswersStateAsh::UpdateDefinitionEnabled,
base::Unretained(this)));
pref_change_registrar_->Add(
kQuickAnswersTranslationEnabled,
base::BindRepeating(&QuickAnswersStateAsh::UpdateTranslationEnabled,
base::Unretained(this)));
pref_change_registrar_->Add(
kQuickAnswersUnitConversionEnabled,
base::BindRepeating(&QuickAnswersStateAsh::UpdateUnitConversionEnabled,
base::Unretained(this)));
pref_change_registrar_->Add(
language::prefs::kApplicationLocale,
base::BindRepeating(&QuickAnswersStateAsh::OnApplicationLocaleReady,
base::Unretained(this)));
pref_change_registrar_->Add(
language::prefs::kPreferredLanguages,
base::BindRepeating(&QuickAnswersStateAsh::UpdatePreferredLanguages,
base::Unretained(this)));
pref_change_registrar_->Add(
ash::prefs::kAccessibilitySpokenFeedbackEnabled,
base::BindRepeating(&QuickAnswersStateAsh::UpdateSpokenFeedbackEnabled,
base::Unretained(this)));
pref_change_registrar_->Add(
kQuickAnswersNoticeImpressionCount,
base::BindRepeating(&QuickAnswersStateAsh::UpdateNoticeImpressionCount,
base::Unretained(this)));
UpdateSettingsEnabled();
UpdateConsentStatus();
UpdateDefinitionEnabled();
UpdateTranslationEnabled();
UpdateUnitConversionEnabled();
OnApplicationLocaleReady();
UpdatePreferredLanguages();
UpdateSpokenFeedbackEnabled();
UpdateNoticeImpressionCount();
prefs_initialized_ = true;
for (auto& observer : observers_) {
observer.OnPrefsInitialized();
}
quick_answers::RecordFeatureEnabled(IsEnabledAs(FeatureType::kQuickAnswers));
MaybeNotifyEligibilityChanged();
MaybeNotifyIsEnabledChanged();
}
void QuickAnswersStateAsh::AsyncWriteConsentUiImpressionCount(int32_t count) {
pref_change_registrar_->prefs()->SetInteger(
kQuickAnswersNoticeImpressionCount, count);
}
void QuickAnswersStateAsh::AsyncWriteConsentStatus(
ConsentStatus consent_status) {
pref_change_registrar_->prefs()->SetInteger(kQuickAnswersConsentStatus,
consent_status);
}
void QuickAnswersStateAsh::AsyncWriteEnabled(bool enabled) {
pref_change_registrar_->prefs()->SetBoolean(kQuickAnswersEnabled, enabled);
}
void QuickAnswersStateAsh::UpdateSettingsEnabled() {
auto* prefs = pref_change_registrar_->prefs();
// TODO(b/340628526): modifying a state is error-prone. For example, if a
// state is read before a modification happens, a stale state will be read.
// Instead, each state (e.g., IsEnabled) should be calculated from other
// dependent states (e.g., IsKioskSession).
bool settings_enabled = prefs->GetBoolean(kQuickAnswersEnabled);
// Quick answers should be disabled for kiosk session.
if (chromeos::IsKioskSession()) {
settings_enabled = false;
prefs->SetBoolean(kQuickAnswersEnabled, false);
prefs->SetInteger(kQuickAnswersConsentStatus, ConsentStatus::kRejected);
}
// If the feature is enforced off by the administrator policy, set the
// consented status to rejected. This must be put before the same value return
// below as the default value is `false` and we cannot observe
// unmanaged-disabled to managed-disabled change.
if (!settings_enabled &&
prefs->IsManagedPreference(quick_answers::prefs::kQuickAnswersEnabled)) {
prefs->SetInteger(kQuickAnswersConsentStatus, ConsentStatus::kRejected);
}
bool turned_on = quick_answers_enabled_.has_value() &&
!quick_answers_enabled_.value() && settings_enabled;
// If the user turn on the Quick Answers in settings, set the consented status
// to true.
// TODO(b/340628526): move this logic to `QuickAnswersState`.
// `QuickAnswersStateAsh` should only have logic unique to ash. Plan is to
// make `quick_answers_enabled_` as private and add void
// SetQuickAnswersEnabled(bool) as a protected method.
if (turned_on) {
CHECK(quick_answers_enabled_.has_value());
CHECK(!quick_answers_enabled_.value());
CHECK(settings_enabled);
prefs->SetInteger(kQuickAnswersConsentStatus, ConsentStatus::kAccepted);
}
quick_answers_enabled_ = settings_enabled;
MaybeNotifyIsEnabledChanged();
}
void QuickAnswersStateAsh::UpdateConsentStatus() {
auto consent_status = static_cast<ConsentStatus>(
pref_change_registrar_->prefs()->GetInteger(kQuickAnswersConsentStatus));
SetQuickAnswersFeatureConsentStatus(consent_status);
}
void QuickAnswersStateAsh::UpdateDefinitionEnabled() {
auto definition_enabled = pref_change_registrar_->prefs()->GetBoolean(
kQuickAnswersDefinitionEnabled);
// See a comment of `QuickAnswersState::IsIntentEligible` for the reason of
// this is called as eligible instead of enabled.
SetIntentEligibilityAsQuickAnswers(quick_answers::Intent::kDefinition,
definition_enabled);
}
void QuickAnswersStateAsh::UpdateTranslationEnabled() {
auto translation_enabled = pref_change_registrar_->prefs()->GetBoolean(
kQuickAnswersTranslationEnabled);
// See a comment of `QuickAnswersState::IsIntentEligible` for the reason of
// this is called as eligible instead of enabled.
SetIntentEligibilityAsQuickAnswers(quick_answers::Intent::kTranslation,
translation_enabled);
}
void QuickAnswersStateAsh::UpdateUnitConversionEnabled() {
auto unit_conversion_enabled = pref_change_registrar_->prefs()->GetBoolean(
kQuickAnswersUnitConversionEnabled);
// See a comment of `QuickAnswersState::IsIntentEligible` for the reason of
// this is called as eligible instead of enabled.
SetIntentEligibilityAsQuickAnswers(quick_answers::Intent::kUnitConversion,
unit_conversion_enabled);
}
void QuickAnswersStateAsh::OnApplicationLocaleReady() {
auto locale = pref_change_registrar_->prefs()->GetString(
language::prefs::kApplicationLocale);
if (locale.empty()) {
return;
}
// We should not directly use the pref locale, resolve the generic locale name
// to one of the locally defined ones first.
std::string resolved_locale;
bool resolve_success =
l10n_util::CheckAndResolveLocale(locale, &resolved_locale,
/*perform_io=*/false);
DCHECK(resolve_success);
if (resolved_application_locale_ == resolved_locale) {
return;
}
resolved_application_locale_ = resolved_locale;
for (auto& observer : observers_) {
observer.OnApplicationLocaleReady(resolved_locale);
}
MaybeNotifyEligibilityChanged();
}
void QuickAnswersStateAsh::UpdatePreferredLanguages() {
auto preferred_languages = pref_change_registrar_->prefs()->GetString(
language::prefs::kPreferredLanguages);
preferred_languages_ = preferred_languages;
for (auto& observer : observers_) {
observer.OnPreferredLanguagesChanged(preferred_languages);
}
}
void QuickAnswersStateAsh::UpdateSpokenFeedbackEnabled() {
auto spoken_feedback_enabled = pref_change_registrar_->prefs()->GetBoolean(
ash::prefs::kAccessibilitySpokenFeedbackEnabled);
spoken_feedback_enabled_ = spoken_feedback_enabled;
}
void QuickAnswersStateAsh::UpdateNoticeImpressionCount() {
consent_ui_impression_count_ = pref_change_registrar_->prefs()->GetInteger(
kQuickAnswersNoticeImpressionCount);
}