// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/policy/policy_recommendation_restorer.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/ash_prefs.h"
#include "ash/session/session_controller_impl.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "components/prefs/pref_notifier_impl.h"
#include "components/prefs/testing_pref_store.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "ui/base/user_activity/user_activity_detector.h"
namespace ash {
class PolicyRecommendationRestorerTest : public NoSessionAshTestBase {
public:
PolicyRecommendationRestorerTest(const PolicyRecommendationRestorerTest&) =
delete;
PolicyRecommendationRestorerTest& operator=(
const PolicyRecommendationRestorerTest&) = delete;
protected:
PolicyRecommendationRestorerTest()
: recommended_prefs_(base::MakeRefCounted<TestingPrefStore>()),
prefs_(new sync_preferences::TestingPrefServiceSyncable(
/*managed_prefs=*/base::MakeRefCounted<TestingPrefStore>(),
/*supervised_user_prefs=*/base::MakeRefCounted<TestingPrefStore>(),
/*extension_prefs=*/base::MakeRefCounted<TestingPrefStore>(),
/*standalone_browser_prefs=*/
base::MakeRefCounted<TestingPrefStore>(),
/*user_prefs=*/base::MakeRefCounted<TestingPrefStore>(),
recommended_prefs_,
base::MakeRefCounted<user_prefs::PrefRegistrySyncable>(),
std::make_unique<PrefNotifierImpl>())) {}
~PolicyRecommendationRestorerTest() override = default;
// NoSessionAshTestBase override:
void SetUp() override {
TestSessionControllerClient::DisableAutomaticallyProvideSigninPref();
NoSessionAshTestBase::SetUp();
// Register sigin prefs but not connected to pref service yet. This allows
// us set pref values before ash connects to pref service for testing.
RegisterSigninProfilePrefs(prefs_->registry(), /*country=*/"",
/*for_test=*/true);
restorer_ = Shell::Get()->policy_recommendation_restorer();
}
void ConnectToSigninPrefService() {
GetSessionControllerClient()->SetSigninScreenPrefService(
base::WrapUnique(prefs_.get()));
ASSERT_EQ(Shell::Get()->session_controller()->GetSigninScreenPrefService(),
prefs_);
// Manually trigger a user activity, so that the delay is not skipped due to
// no user input since a pref is started observing recommended value. See
// PolicyRecommendationRestorer::Restore() for the information.
ui::UserActivityDetector::Get()->HandleExternalUserActivity();
}
void SetRecommendedValues() {
recommended_prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled,
false);
recommended_prefs_->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled,
false);
recommended_prefs_->SetBoolean(prefs::kAccessibilityHighContrastEnabled,
false);
recommended_prefs_->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled,
false);
recommended_prefs_->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
false);
}
void SetUserSettings() {
prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, true);
prefs_->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, true);
prefs_->SetBoolean(prefs::kAccessibilityHighContrastEnabled, true);
prefs_->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled, true);
prefs_->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled, true);
}
void VerifyPrefFollowsUser(const std::string& pref_name,
const base::Value& expected_value) const {
const sync_preferences::PrefServiceSyncable::Preference* pref =
prefs_->FindPreference(pref_name);
ASSERT_TRUE(pref);
EXPECT_TRUE(pref->HasUserSetting());
const base::Value* value = pref->GetValue();
ASSERT_TRUE(value);
EXPECT_EQ(expected_value, *value);
}
void VerifyPrefsFollowUser() const {
VerifyPrefFollowsUser(prefs::kAccessibilityLargeCursorEnabled,
base::Value(true));
VerifyPrefFollowsUser(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(true));
VerifyPrefFollowsUser(prefs::kAccessibilityHighContrastEnabled,
base::Value(true));
VerifyPrefFollowsUser(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(true));
VerifyPrefFollowsUser(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(true));
}
void VerifyPrefFollowsRecommendation(
const std::string& pref_name,
const base::Value& expected_value) const {
const sync_preferences::PrefServiceSyncable::Preference* pref =
prefs_->FindPreference(pref_name);
ASSERT_TRUE(pref);
EXPECT_TRUE(pref->IsRecommended());
EXPECT_FALSE(pref->HasUserSetting());
const base::Value* value = pref->GetValue();
ASSERT_TRUE(value);
EXPECT_EQ(expected_value, *value);
}
void VerifyPrefsFollowRecommendation() const {
VerifyPrefFollowsRecommendation(prefs::kAccessibilityLargeCursorEnabled,
base::Value(false));
VerifyPrefFollowsRecommendation(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(false));
VerifyPrefFollowsRecommendation(prefs::kAccessibilityHighContrastEnabled,
base::Value(false));
VerifyPrefFollowsRecommendation(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(false));
VerifyPrefFollowsRecommendation(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(false));
}
bool RestoreTimerIsRunning() {
return restorer_->restore_timer_for_test()->IsRunning();
}
// If restore timer is running, stops it, runs its task and returns true.
// Otherwise, returns false.
[[nodiscard]] bool TriggerRestoreTimer() {
if (!restorer_->restore_timer_for_test()->IsRunning())
return false;
restorer_->restore_timer_for_test()->FireNow();
return true;
}
raw_ptr<PolicyRecommendationRestorer, DanglingUntriaged> restorer_ = nullptr;
// Ownerships are passed to SessionController.
scoped_refptr<TestingPrefStore> recommended_prefs_;
raw_ptr<sync_preferences::TestingPrefServiceSyncable, DanglingUntriaged>
prefs_;
};
// Verifies that when no recommended values have been set, |restorer_| does not
// clear user settings on initialization and does not start a timer that will
// clear user settings eventually.
TEST_F(PolicyRecommendationRestorerTest, NoRecommendations) {
SetUserSettings();
ConnectToSigninPrefService();
VerifyPrefsFollowUser();
EXPECT_FALSE(RestoreTimerIsRunning());
}
// Verifies that when recommended values have been set, |restorer_| clears user
// settings on signing pref initialization.
TEST_F(PolicyRecommendationRestorerTest, RestoreOnSigninPrefInitialized) {
SetRecommendedValues();
SetUserSettings();
ConnectToSigninPrefService();
VerifyPrefsFollowRecommendation();
EXPECT_FALSE(RestoreTimerIsRunning());
}
// Verifies that if recommended values change while the signin screen is being
// shown, a timer is started that will clear user settings eventually.
TEST_F(PolicyRecommendationRestorerTest,
RestoreOnRecommendationChangeOnSigninScreen) {
// No recommended values in observed prefs.
ConnectToSigninPrefService();
EXPECT_FALSE(RestoreTimerIsRunning());
prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityLargeCursorEnabled,
base::Value(true));
EXPECT_FALSE(RestoreTimerIsRunning());
recommended_prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled,
false);
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityLargeCursorEnabled,
base::Value(false));
prefs_->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled,
false);
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(false));
prefs_->SetBoolean(prefs::kAccessibilityHighContrastEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityHighContrastEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilityHighContrastEnabled,
false);
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityHighContrastEnabled,
base::Value(false));
prefs_->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled,
false);
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(false));
prefs_->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
false);
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(false));
}
// Verifies that if recommended values change while a user session is in
// progress, user settings are cleared immediately.
TEST_F(PolicyRecommendationRestorerTest,
RestoreOnRecommendationChangeOnUserSession) {
SetUserSettings();
ConnectToSigninPrefService();
SimulateUserLogin("[email protected]");
VerifyPrefFollowsUser(prefs::kAccessibilityLargeCursorEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled,
false);
EXPECT_FALSE(RestoreTimerIsRunning());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityLargeCursorEnabled,
base::Value(false));
VerifyPrefFollowsUser(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled,
false);
EXPECT_FALSE(RestoreTimerIsRunning());
VerifyPrefFollowsRecommendation(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(false));
VerifyPrefFollowsUser(prefs::kAccessibilityHighContrastEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilityHighContrastEnabled,
false);
EXPECT_FALSE(RestoreTimerIsRunning());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityHighContrastEnabled,
base::Value(false));
VerifyPrefFollowsUser(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled,
false);
EXPECT_FALSE(RestoreTimerIsRunning());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(false));
VerifyPrefFollowsUser(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(true));
recommended_prefs_->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled,
false);
EXPECT_FALSE(RestoreTimerIsRunning());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(false));
}
// Verifies that if no recommended values have been set and user settings
// change, the user settings are not cleared immediately and no timer is started
// that will clear the user settings eventually.
TEST_F(PolicyRecommendationRestorerTest, DoNothingOnUserChange) {
ConnectToSigninPrefService();
// a11y mono audio is not observing recommended values in production.
prefs_->SetBoolean(prefs::kAccessibilityMonoAudioEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityMonoAudioEnabled,
base::Value(true));
EXPECT_FALSE(RestoreTimerIsRunning());
}
TEST_F(PolicyRecommendationRestorerTest, RestoreOnUserChange) {
SetRecommendedValues();
ConnectToSigninPrefService();
EXPECT_FALSE(RestoreTimerIsRunning());
prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityLargeCursorEnabled,
base::Value(true));
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityLargeCursorEnabled,
base::Value(false));
EXPECT_FALSE(RestoreTimerIsRunning());
prefs_->SetBoolean(prefs::kAccessibilitySpokenFeedbackEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(true));
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilitySpokenFeedbackEnabled,
base::Value(false));
EXPECT_FALSE(RestoreTimerIsRunning());
prefs_->SetBoolean(prefs::kAccessibilityHighContrastEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityHighContrastEnabled,
base::Value(true));
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityHighContrastEnabled,
base::Value(false));
EXPECT_FALSE(RestoreTimerIsRunning());
prefs_->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(true));
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityScreenMagnifierEnabled,
base::Value(false));
EXPECT_FALSE(RestoreTimerIsRunning());
prefs_->SetBoolean(prefs::kAccessibilityVirtualKeyboardEnabled, true);
VerifyPrefFollowsUser(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(true));
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityVirtualKeyboardEnabled,
base::Value(false));
EXPECT_FALSE(RestoreTimerIsRunning());
}
// Verifies that if recommended values have not been set, user settings have
// changed and a session is then started, the user settings are not cleared
// immediately.
TEST_F(PolicyRecommendationRestorerTest, DoNothingOnSessionStart) {
ConnectToSigninPrefService();
SetUserSettings();
SimulateUserLogin("[email protected]");
VerifyPrefsFollowUser();
EXPECT_FALSE(RestoreTimerIsRunning());
}
// Verifies that user activity resets the timer which clears user settings.
TEST_F(PolicyRecommendationRestorerTest, UserActivityResetsTimer) {
recommended_prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled,
false);
ConnectToSigninPrefService();
prefs_->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, true);
EXPECT_TRUE(RestoreTimerIsRunning());
// Notify that there is user activity.
restorer_->OnUserActivity(nullptr);
VerifyPrefFollowsUser(prefs::kAccessibilityLargeCursorEnabled,
base::Value(true));
// Trigger reset timer to timeout.
EXPECT_TRUE(TriggerRestoreTimer());
VerifyPrefFollowsRecommendation(prefs::kAccessibilityLargeCursorEnabled,
base::Value(false));
EXPECT_FALSE(RestoreTimerIsRunning());
}
} // namespace ash