chromium/chrome/browser/ui/quick_answers/quick_answers_state_ash_unittest.cc

// 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 "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/quick_answers/test/chrome_quick_answers_test_base.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/components/kiosk/kiosk_test_utils.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/public/cpp/quick_answers_state.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "third_party/icu/source/common/unicode/locid.h"

namespace {

using quick_answers::prefs::ConsentStatus;

constexpr char kTestUser[] = "[email protected]";

}  // namespace

class TestQuickAnswersStateObserver : public QuickAnswersStateObserver {
 public:
  TestQuickAnswersStateObserver() = default;

  TestQuickAnswersStateObserver(const TestQuickAnswersStateObserver&) = delete;
  TestQuickAnswersStateObserver& operator=(
      const TestQuickAnswersStateObserver&) = delete;

  ~TestQuickAnswersStateObserver() override = default;

  // QuickAnswersStateObserver:
  void OnSettingsEnabled(bool settings_enabled) override {
    settings_enabled_ = settings_enabled;
  }
  void OnConsentStatusUpdated(ConsentStatus status) override {
    consent_status_ = status;
  }
  void OnApplicationLocaleReady(
      const std::string& application_locale) override {
    application_locale_ = application_locale;
  }
  void OnPreferredLanguagesChanged(
      const std::string& preferred_languages) override {
    preferred_languages_ = preferred_languages;
  }
  void OnEligibilityChanged(bool eligible) override { is_eligible_ = eligible; }
  void OnPrefsInitialized() override { prefs_initialized_ = true; }

  bool settings_enabled() const { return settings_enabled_; }
  ConsentStatus consent_status() const { return consent_status_; }
  const std::string& application_locale() const { return application_locale_; }
  const std::string& preferred_languages() const {
    return preferred_languages_;
  }
  bool is_eligible() const { return is_eligible_; }
  bool prefs_initialized() const { return prefs_initialized_; }

 private:
  bool settings_enabled_ = false;
  ConsentStatus consent_status_ = ConsentStatus::kUnknown;
  std::string application_locale_;
  std::string preferred_languages_;
  bool is_eligible_ = false;
  bool prefs_initialized_ = false;
};

// The bool parameter controls the feature flag QuickAnswers.
class QuickAnswersStateAshTest : public ChromeQuickAnswersTestBase,
                                 public testing::WithParamInterface<bool> {
 protected:
  QuickAnswersStateAshTest() = default;
  QuickAnswersStateAshTest(const QuickAnswersStateAshTest&) = delete;
  QuickAnswersStateAshTest& operator=(const QuickAnswersStateAshTest&) = delete;
  ~QuickAnswersStateAshTest() override = default;

  // ChromeQuickAnswersTestBase:
  void SetUp() override {
    ChromeQuickAnswersTestBase::SetUp();
    CHECK(QuickAnswersState::Get()->prefs_initialized());

    observer_ = std::make_unique<TestQuickAnswersStateObserver>();
  }

  void SetUpInitialPrefValues() override {
    prefs_ = static_cast<TestingPrefServiceSimple*>(
        ash::Shell::Get()->session_controller()->GetPrimaryUserPrefService());
    CHECK(prefs_);
  }

  TestingPrefServiceSimple* prefs() { return prefs_; }

  TestQuickAnswersStateObserver* observer() { return observer_.get(); }

 private:
  raw_ptr<TestingPrefServiceSimple, DanglingUntriaged> prefs_ = nullptr;
  std::unique_ptr<TestQuickAnswersStateObserver> observer_;
};

class QuickAnswersStateAshEnabledTest : public QuickAnswersStateAshTest {
 protected:
  void SetUpInitialPrefValues() override {
    QuickAnswersStateAshTest::SetUpInitialPrefValues();

    prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersEnabled, true);
    CHECK_EQ(
        ConsentStatus::kUnknown,
        prefs()->GetInteger(quick_answers::prefs::kQuickAnswersConsentStatus));
  }
};

TEST_F(QuickAnswersStateAshTest, InitObserver) {
  EXPECT_FALSE(QuickAnswersState::IsEnabled());
  EXPECT_EQ(QuickAnswersState::GetConsentStatus(), ConsentStatus::kUnknown);
  EXPECT_EQ(QuickAnswersState::Get()->application_locale(), std::string());

  prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersEnabled, true);
  prefs()->SetInteger(quick_answers::prefs::kQuickAnswersConsentStatus,
                      ConsentStatus::kAccepted);
  const std::string application_locale = "en-US";
  prefs()->SetString(language::prefs::kApplicationLocale, application_locale);

  EXPECT_TRUE(QuickAnswersState::IsEnabled());
  EXPECT_EQ(QuickAnswersState::GetConsentStatus(), ConsentStatus::kAccepted);
  EXPECT_EQ(QuickAnswersState::Get()->application_locale(), application_locale);

  // The observer class should get an instant notification about the current
  // pref value.
  QuickAnswersState::Get()->AddObserver(observer());
  EXPECT_TRUE(observer()->settings_enabled());
  EXPECT_EQ(observer()->consent_status(), ConsentStatus::kAccepted);
  EXPECT_EQ(observer()->application_locale(), application_locale);
  EXPECT_TRUE(observer()->prefs_initialized());

  QuickAnswersState::Get()->RemoveObserver(observer());
}

TEST_F(QuickAnswersStateAshTest, NotifySettingsEnabled) {
  QuickAnswersState::Get()->AddObserver(observer());

  const std::string application_locale = "en-US";
  prefs()->SetString(language::prefs::kApplicationLocale, application_locale);

  EXPECT_FALSE(QuickAnswersState::IsEnabled());
  EXPECT_FALSE(observer()->settings_enabled());
  EXPECT_EQ(QuickAnswersState::GetConsentStatus(),
            quick_answers::prefs::ConsentStatus::kUnknown);

  // The observer class should get an notification when the pref value changes.
  prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersEnabled, true);
  EXPECT_TRUE(QuickAnswersState::IsEnabled());
  EXPECT_TRUE(observer()->settings_enabled());

  // Consent status should also be set to accepted since the feature is
  // explicitly enabled.
  EXPECT_EQ(QuickAnswersState::GetConsentStatus(),
            quick_answers::prefs::ConsentStatus::kAccepted);

  QuickAnswersState::Get()->RemoveObserver(observer());
}

TEST_F(QuickAnswersStateAshTest, UpdateConsentStatus) {
  QuickAnswersState::Get()->AddObserver(observer());

  EXPECT_EQ(QuickAnswersState::GetConsentStatus(),
            quick_answers::prefs::ConsentStatus::kUnknown);
  EXPECT_EQ(observer()->consent_status(),
            quick_answers::prefs::ConsentStatus::kUnknown);

  // The observer class should get an notification when the pref value changes.
  prefs()->SetInteger(quick_answers::prefs::kQuickAnswersConsentStatus,
                      quick_answers::prefs::ConsentStatus::kRejected);
  EXPECT_EQ(QuickAnswersState::GetConsentStatus(),
            quick_answers::prefs::ConsentStatus::kRejected);
  EXPECT_EQ(observer()->consent_status(),
            quick_answers::prefs::ConsentStatus::kRejected);

  prefs()->SetInteger(quick_answers::prefs::kQuickAnswersConsentStatus,
                      quick_answers::prefs::ConsentStatus::kAccepted);
  EXPECT_EQ(QuickAnswersState::GetConsentStatus(),
            quick_answers::prefs::ConsentStatus::kAccepted);
  EXPECT_EQ(observer()->consent_status(),
            quick_answers::prefs::ConsentStatus::kAccepted);

  QuickAnswersState::Get()->RemoveObserver(observer());
}

TEST_F(QuickAnswersStateAshTest, UpdateDefinitionEnabled) {
  const std::string application_locale = "en-US";
  prefs()->SetString(language::prefs::kApplicationLocale, application_locale);

  // Definition subfeature is default on.
  EXPECT_TRUE(
      QuickAnswersState::IsIntentEligible(quick_answers::Intent::kDefinition));

  prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersDefinitionEnabled,
                      false);
  EXPECT_FALSE(
      QuickAnswersState::IsIntentEligible(quick_answers::Intent::kDefinition));
}

TEST_F(QuickAnswersStateAshTest, UpdateTranslationEnabled) {
  const std::string application_locale = "en-US";
  prefs()->SetString(language::prefs::kApplicationLocale, application_locale);

  // Translation subfeature is default on.
  EXPECT_TRUE(
      QuickAnswersState::IsIntentEligible(quick_answers::Intent::kTranslation));

  prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersTranslationEnabled,
                      false);
  EXPECT_FALSE(
      QuickAnswersState::IsIntentEligible(quick_answers::Intent::kTranslation));
}

TEST_F(QuickAnswersStateAshTest, UpdateUnitConversionEnabled) {
  const std::string application_locale = "en-US";
  prefs()->SetString(language::prefs::kApplicationLocale, application_locale);

  // Unit conversion subfeature is default on.
  EXPECT_TRUE(QuickAnswersState::IsIntentEligible(
      quick_answers::Intent::kUnitConversion));

  prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersUnitConversionEnabled,
                      false);
  EXPECT_FALSE(QuickAnswersState::IsIntentEligible(
      quick_answers::Intent::kUnitConversion));
}

TEST_F(QuickAnswersStateAshTest, NotifyApplicationLocaleReady) {
  QuickAnswersState::Get()->AddObserver(observer());

  EXPECT_TRUE(QuickAnswersState::Get()->application_locale().empty());
  EXPECT_TRUE(observer()->application_locale().empty());

  const std::string application_locale = "en-US";

  // The observer class should get an notification when the pref value changes.
  prefs()->SetString(language::prefs::kApplicationLocale, application_locale);
  EXPECT_EQ(QuickAnswersState::Get()->application_locale(), application_locale);
  EXPECT_EQ(observer()->application_locale(), application_locale);

  QuickAnswersState::Get()->RemoveObserver(observer());
}

TEST_F(QuickAnswersStateAshTest, UpdatePreferredLanguages) {
  QuickAnswersState::Get()->AddObserver(observer());

  EXPECT_TRUE(QuickAnswersState::Get()->preferred_languages().empty());
  EXPECT_TRUE(observer()->preferred_languages().empty());

  const std::string preferred_languages = "en-US,zh";
  prefs()->SetString(language::prefs::kPreferredLanguages, preferred_languages);
  EXPECT_EQ(QuickAnswersState::Get()->preferred_languages(),
            preferred_languages);
  EXPECT_EQ(observer()->preferred_languages(), preferred_languages);

  QuickAnswersState::Get()->RemoveObserver(observer());
}

TEST_F(QuickAnswersStateAshTest, UpdateSpokenFeedbackEnabled) {
  EXPECT_FALSE(QuickAnswersState::Get()->spoken_feedback_enabled());

  prefs()->SetBoolean(ash::prefs::kAccessibilitySpokenFeedbackEnabled, true);
  EXPECT_TRUE(QuickAnswersState::Get()->spoken_feedback_enabled());
}

TEST_F(QuickAnswersStateAshTest, EligibleLocales) {
  QuickAnswersState::Get()->AddObserver(observer());

  EXPECT_FALSE(QuickAnswersState::IsEligible());
  EXPECT_FALSE(observer()->is_eligible());

  prefs()->SetString(language::prefs::kApplicationLocale, "pt");
  SimulateUserLogin(kTestUser);
  EXPECT_TRUE(QuickAnswersState::IsEligible());
  EXPECT_TRUE(observer()->is_eligible());

  ClearLogin();

  prefs()->SetString(language::prefs::kApplicationLocale, "en");
  SimulateUserLogin(kTestUser);
  EXPECT_TRUE(QuickAnswersState::IsEligible());
  EXPECT_TRUE(observer()->is_eligible());
}

TEST_F(QuickAnswersStateAshTest, IneligibleLocales) {
  QuickAnswersState::Get()->AddObserver(observer());

  EXPECT_FALSE(QuickAnswersState::IsEligible());
  EXPECT_FALSE(observer()->is_eligible());

  prefs()->SetString(language::prefs::kApplicationLocale, "zh");
  SimulateUserLogin(kTestUser);
  EXPECT_FALSE(QuickAnswersState::IsEligible());
  EXPECT_FALSE(observer()->is_eligible());

  ClearLogin();

  prefs()->SetString(language::prefs::kApplicationLocale, "ja");
  SimulateUserLogin(kTestUser);
  EXPECT_FALSE(QuickAnswersState::IsEligible());
  EXPECT_FALSE(observer()->is_eligible());
}

TEST_F(QuickAnswersStateAshTest, DisabledByPolicy) {
  QuickAnswersState::Get()->AddObserver(observer());

  ASSERT_FALSE(
      prefs()->IsManagedPreference(quick_answers::prefs::kQuickAnswersEnabled));
  ASSERT_FALSE(prefs()->GetBoolean(quick_answers::prefs::kQuickAnswersEnabled));
  ASSERT_EQ(quick_answers::prefs::ConsentStatus::kUnknown,
            observer()->consent_status());

  prefs()->SetManagedPref(quick_answers::prefs::kQuickAnswersEnabled,
                          base::Value(false));
  EXPECT_EQ(quick_answers::prefs::ConsentStatus::kRejected,
            observer()->consent_status());
  EXPECT_FALSE(observer()->settings_enabled());
}

TEST_F(QuickAnswersStateAshTest, EnabledThenDisabledByPolicy) {
  QuickAnswersState::Get()->AddObserver(observer());

  prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersEnabled, true);
  ASSERT_FALSE(
      prefs()->IsManagedPreference(quick_answers::prefs::kQuickAnswersEnabled));
  ASSERT_TRUE(prefs()->GetBoolean(quick_answers::prefs::kQuickAnswersEnabled));
  ASSERT_EQ(quick_answers::prefs::ConsentStatus::kAccepted,
            observer()->consent_status());

  prefs()->SetManagedPref(quick_answers::prefs::kQuickAnswersEnabled,
                          base::Value(false));
  EXPECT_EQ(quick_answers::prefs::ConsentStatus::kRejected,
            observer()->consent_status());
  EXPECT_FALSE(observer()->settings_enabled());
}

// This is for testing `turned_on` in
// `QuickAnswersStateAsh::UpdateSettingsEnabled`.
TEST_F(QuickAnswersStateAshEnabledTest, EnabledFromBeginning) {
  ASSERT_TRUE(prefs()->GetBoolean(quick_answers::prefs::kQuickAnswersEnabled));

  EXPECT_EQ(
      ConsentStatus::kUnknown,
      prefs()->GetInteger(quick_answers::prefs::kQuickAnswersConsentStatus))
      << "If pref value is enabled from beginning, it should not be treated as "
         "turned on, i.e., consent status must be un-touched.";
}

TEST_P(QuickAnswersStateAshTest, ForceDisabledForKiosk) {
  QuickAnswersState::Get()->AddObserver(observer());

  user_manager::ScopedUserManager user_manager(
      std::make_unique<user_manager::FakeUserManager>());
  chromeos::SetUpFakeKioskSession();

  EXPECT_TRUE(chromeos::IsKioskSession());
  prefs()->SetBoolean(quick_answers::prefs::kQuickAnswersEnabled, GetParam());

  EXPECT_FALSE(
      prefs()->IsManagedPreference(quick_answers::prefs::kQuickAnswersEnabled));
  EXPECT_EQ(quick_answers::prefs::ConsentStatus::kRejected,
            observer()->consent_status());
  EXPECT_FALSE(observer()->settings_enabled());
}

INSTANTIATE_TEST_SUITE_P(ForceDisabledForKiosk,
                         QuickAnswersStateAshTest,
                         testing::Bool());