| // Copyright 2017 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 "ash/system/night_light/night_light_controller.h" |
| |
| #include <cmath> |
| #include <limits> |
| #include <sstream> |
| #include <string> |
| |
| #include "ash/display/cursor_window_controller.h" |
| #include "ash/display/window_tree_host_manager.h" |
| #include "ash/public/cpp/ash_pref_names.h" |
| #include "ash/public/cpp/session/session_types.h" |
| #include "ash/root_window_controller.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 "ash/test/ash_test_helper.h" |
| #include "ash/test_shell_delegate.h" |
| #include "base/bind.h" |
| #include "base/callback_forward.h" |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/pattern.h" |
| #include "components/prefs/pref_service.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/fake/fake_display_snapshot.h" |
| #include "ui/display/manager/display_change_observer.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/test/action_logger_util.h" |
| #include "ui/display/manager/test/test_native_display_delegate.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr char kUser1Email[] = "user1@nightlight"; |
| constexpr char kUser2Email[] = "user2@nightlight"; |
| |
| NightLightController* GetController() { |
| return Shell::Get()->night_light_controller(); |
| } |
| |
| // Tests that the given display with |display_id| has the expected color matrix |
| // on its compositor that corresponds to the given |temperature|. |
| void TestDisplayCompositorTemperature(int64_t display_id, float temperature) { |
| WindowTreeHostManager* wth_manager = Shell::Get()->window_tree_host_manager(); |
| aura::Window* root_window = |
| wth_manager->GetRootWindowForDisplayId(display_id); |
| DCHECK(root_window); |
| aura::WindowTreeHost* host = root_window->GetHost(); |
| DCHECK(host); |
| ui::Compositor* compositor = host->compositor(); |
| DCHECK(compositor); |
| |
| const SkMatrix44& matrix = compositor->display_color_matrix(); |
| const float blue_scale = matrix.get(2, 2); |
| const float green_scale = matrix.get(1, 1); |
| EXPECT_FLOAT_EQ( |
| blue_scale, |
| NightLightController::BlueColorScaleFromTemperature(temperature)); |
| EXPECT_FLOAT_EQ(green_scale, |
| NightLightController::GreenColorScaleFromTemperature( |
| temperature, false /* in_linear_gamma_space */)); |
| } |
| |
| // Tests that the display color matrices of all compositors correctly correspond |
| // to the given |temperature|. |
| void TestCompositorsTemperature(float temperature) { |
| for (int64_t display_id : |
| Shell::Get()->display_manager()->GetCurrentDisplayIdList()) { |
| TestDisplayCompositorTemperature(display_id, temperature); |
| } |
| } |
| |
| class TestObserver : public NightLightController::Observer { |
| public: |
| TestObserver() { GetController()->AddObserver(this); } |
| ~TestObserver() override { GetController()->RemoveObserver(this); } |
| |
| // ash::NightLightController::Observer: |
| void OnNightLightEnabledChanged(bool enabled) override { status_ = enabled; } |
| |
| bool status() const { return status_; } |
| |
| private: |
| bool status_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestObserver); |
| }; |
| |
| constexpr double kFakePosition1_Latitude = 23.5; |
| constexpr double kFakePosition1_Longitude = 35.88; |
| constexpr int kFakePosition1_SunsetOffset = 20 * 60; |
| constexpr int kFakePosition1_SunriseOffset = 4 * 60; |
| |
| constexpr double kFakePosition2_Latitude = 37.5; |
| constexpr double kFakePosition2_Longitude = -100.5; |
| constexpr int kFakePosition2_SunsetOffset = 17 * 60; |
| constexpr int kFakePosition2_SunriseOffset = 3 * 60; |
| |
| class TestDelegate : public NightLightController::Delegate { |
| public: |
| TestDelegate() = default; |
| ~TestDelegate() override = default; |
| |
| void SetFakeNow(TimeOfDay time) { fake_now_ = time.ToTimeToday(); } |
| void SetFakeSunset(TimeOfDay time) { fake_sunset_ = time.ToTimeToday(); } |
| void SetFakeSunrise(TimeOfDay time) { fake_sunrise_ = time.ToTimeToday(); } |
| |
| // ash::NightLightController::Delegate |
| base::Time GetNow() const override { return fake_now_; } |
| base::Time GetSunsetTime() const override { return fake_sunset_; } |
| base::Time GetSunriseTime() const override { return fake_sunrise_; } |
| void SetGeoposition(mojom::SimpleGeopositionPtr position) override { |
| has_geoposition_ = true; |
| if (position.Equals(mojom::SimpleGeoposition::New( |
| kFakePosition1_Latitude, kFakePosition1_Longitude))) { |
| // Set sunset and sunrise times associated with fake position 1. |
| SetFakeSunset(TimeOfDay(kFakePosition1_SunsetOffset)); |
| SetFakeSunrise(TimeOfDay(kFakePosition1_SunriseOffset)); |
| } else if (position.Equals(mojom::SimpleGeoposition::New( |
| kFakePosition2_Latitude, kFakePosition2_Longitude))) { |
| // Set sunset and sunrise times associated with fake position 2. |
| SetFakeSunset(TimeOfDay(kFakePosition2_SunsetOffset)); |
| SetFakeSunrise(TimeOfDay(kFakePosition2_SunriseOffset)); |
| } |
| } |
| bool HasGeoposition() const override { return has_geoposition_; } |
| |
| private: |
| base::Time fake_now_; |
| base::Time fake_sunset_; |
| base::Time fake_sunrise_; |
| bool has_geoposition_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestDelegate); |
| }; |
| |
| class NightLightTest : public NoSessionAshTestBase { |
| public: |
| NightLightTest() = default; |
| ~NightLightTest() override = default; |
| |
| PrefService* user1_pref_service() { |
| return Shell::Get()->session_controller()->GetUserPrefServiceForUser( |
| AccountId::FromUserEmail(kUser1Email)); |
| } |
| |
| PrefService* user2_pref_service() { |
| return Shell::Get()->session_controller()->GetUserPrefServiceForUser( |
| AccountId::FromUserEmail(kUser2Email)); |
| } |
| |
| TestDelegate* delegate() const { return delegate_; } |
| |
| // AshTestBase: |
| void SetUp() override { |
| NoSessionAshTestBase::SetUp(); |
| CreateTestUserSessions(); |
| |
| // Simulate user 1 login. |
| SwitchActiveUser(kUser1Email); |
| |
| delegate_ = new TestDelegate; |
| GetController()->SetDelegateForTesting(base::WrapUnique(delegate_)); |
| } |
| |
| void CreateTestUserSessions() { |
| GetSessionControllerClient()->Reset(); |
| GetSessionControllerClient()->AddUserSession(kUser1Email); |
| GetSessionControllerClient()->AddUserSession(kUser2Email); |
| } |
| |
| void SwitchActiveUser(const std::string& email) { |
| GetSessionControllerClient()->SwitchActiveUser( |
| AccountId::FromUserEmail(email)); |
| } |
| |
| void SetNightLightEnabled(bool enabled) { |
| GetController()->SetEnabled( |
| enabled, NightLightController::AnimationDuration::kShort); |
| } |
| |
| private: |
| TestDelegate* delegate_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(NightLightTest); |
| }; |
| |
| // Tests toggling NightLight on / off and makes sure the observer is updated and |
| // the layer temperatures are modified. |
| TEST_F(NightLightTest, TestToggle) { |
| UpdateDisplay("800x600,800x600"); |
| |
| TestObserver observer; |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(false); |
| ASSERT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| controller->Toggle(); |
| EXPECT_TRUE(controller->GetEnabled()); |
| EXPECT_TRUE(observer.status()); |
| TestCompositorsTemperature(GetController()->GetColorTemperature()); |
| controller->Toggle(); |
| EXPECT_FALSE(controller->GetEnabled()); |
| EXPECT_FALSE(observer.status()); |
| TestCompositorsTemperature(0.0f); |
| } |
| |
| // Tests setting the temperature in various situations. |
| TEST_F(NightLightTest, TestSetTemperature) { |
| UpdateDisplay("800x600,800x600"); |
| |
| TestObserver observer; |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(false); |
| ASSERT_FALSE(controller->GetEnabled()); |
| |
| // Setting the temperature while NightLight is disabled only changes the |
| // color temperature field, but root layers temperatures are not affected, nor |
| // the status of NightLight itself. |
| const float temperature1 = 0.2f; |
| controller->SetColorTemperature(temperature1); |
| EXPECT_EQ(temperature1, controller->GetColorTemperature()); |
| TestCompositorsTemperature(0.0f); |
| |
| // When NightLight is enabled, temperature changes actually affect the root |
| // layers temperatures. |
| SetNightLightEnabled(true); |
| ASSERT_TRUE(controller->GetEnabled()); |
| const float temperature2 = 0.7f; |
| controller->SetColorTemperature(temperature2); |
| EXPECT_EQ(temperature2, controller->GetColorTemperature()); |
| TestCompositorsTemperature(temperature2); |
| |
| // When NightLight is disabled, the value of the color temperature field |
| // doesn't change, however the temperatures set on the root layers are all |
| // 0.0f. Observers only receive an enabled status change notification; no |
| // temperature change notification. |
| SetNightLightEnabled(false); |
| ASSERT_FALSE(controller->GetEnabled()); |
| EXPECT_FALSE(observer.status()); |
| EXPECT_EQ(temperature2, controller->GetColorTemperature()); |
| TestCompositorsTemperature(0.0f); |
| |
| // When re-enabled, the stored temperature is re-applied. |
| SetNightLightEnabled(true); |
| EXPECT_TRUE(observer.status()); |
| ASSERT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(temperature2); |
| } |
| |
| TEST_F(NightLightTest, TestNightLightWithDisplayConfigurationChanges) { |
| // Start with one display and enable NightLight. |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(true); |
| ASSERT_TRUE(controller->GetEnabled()); |
| const float temperature = 0.2f; |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| TestCompositorsTemperature(temperature); |
| |
| // Add a new display, and expect that its compositor gets the already set from |
| // before color temperature. |
| display_manager()->AddRemoveDisplay(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(2u, RootWindowController::root_window_controllers().size()); |
| TestCompositorsTemperature(temperature); |
| |
| // While we have the second display, enable mirror mode, the compositors |
| // should still have the same temperature. |
| display_manager()->SetMirrorMode(display::MirrorMode::kNormal, base::nullopt); |
| EXPECT_TRUE(display_manager()->IsInMirrorMode()); |
| base::RunLoop().RunUntilIdle(); |
| TestCompositorsTemperature(temperature); |
| |
| // Exit mirror mode, temperature is still applied. |
| display_manager()->SetMirrorMode(display::MirrorMode::kOff, base::nullopt); |
| EXPECT_FALSE(display_manager()->IsInMirrorMode()); |
| base::RunLoop().RunUntilIdle(); |
| TestCompositorsTemperature(temperature); |
| |
| // Enter unified mode, temperature is still applied. |
| display_manager()->SetUnifiedDesktopEnabled(true); |
| EXPECT_TRUE(display_manager()->IsInUnifiedMode()); |
| base::RunLoop().RunUntilIdle(); |
| TestCompositorsTemperature(temperature); |
| // The unified host should never have a temperature on its compositor. |
| TestDisplayCompositorTemperature(display::kUnifiedDisplayId, 0.0f); |
| |
| // Exit unified mode, and remove the display, temperature should remain the |
| // same. |
| display_manager()->SetUnifiedDesktopEnabled(false); |
| EXPECT_FALSE(display_manager()->IsInUnifiedMode()); |
| base::RunLoop().RunUntilIdle(); |
| TestCompositorsTemperature(temperature); |
| |
| display_manager()->AddRemoveDisplay(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(1u, RootWindowController::root_window_controllers().size()); |
| TestCompositorsTemperature(temperature); |
| } |
| |
| // Tests that switching users retrieves NightLight settings for the active |
| // user's prefs. |
| TEST_F(NightLightTest, TestUserSwitchAndSettingsPersistence) { |
| // Test start with user1 logged in. |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(true); |
| EXPECT_TRUE(controller->GetEnabled()); |
| const float user1_temperature = 0.8f; |
| controller->SetColorTemperature(user1_temperature); |
| EXPECT_EQ(user1_temperature, controller->GetColorTemperature()); |
| TestCompositorsTemperature(user1_temperature); |
| |
| // Switch to user 2, and expect NightLight to be disabled. |
| SwitchActiveUser(kUser2Email); |
| EXPECT_FALSE(controller->GetEnabled()); |
| // Changing user_2's color temperature shouldn't affect user_1's settings. |
| const float user2_temperature = 0.2f; |
| user2_pref_service()->SetDouble(prefs::kNightLightTemperature, |
| user2_temperature); |
| EXPECT_EQ(user2_temperature, controller->GetColorTemperature()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_EQ(user1_temperature, |
| user1_pref_service()->GetDouble(prefs::kNightLightTemperature)); |
| |
| // Switch back to user 1, to find NightLight is still enabled, and the same |
| // user's color temperature are re-applied. |
| SwitchActiveUser(kUser1Email); |
| EXPECT_TRUE(controller->GetEnabled()); |
| EXPECT_EQ(user1_temperature, controller->GetColorTemperature()); |
| TestCompositorsTemperature(user1_temperature); |
| } |
| |
| // Tests that changes from outside NightLightControlled to the NightLight |
| // Preferences are seen by the controlled and applied properly. |
| TEST_F(NightLightTest, TestOutsidePreferencesChangesAreApplied) { |
| // Test start with user1 logged in. |
| NightLightController* controller = GetController(); |
| user1_pref_service()->SetBoolean(prefs::kNightLightEnabled, true); |
| EXPECT_TRUE(controller->GetEnabled()); |
| const float temperature1 = 0.65f; |
| user1_pref_service()->SetDouble(prefs::kNightLightTemperature, temperature1); |
| EXPECT_EQ(temperature1, controller->GetColorTemperature()); |
| TestCompositorsTemperature(temperature1); |
| const float temperature2 = 0.23f; |
| user1_pref_service()->SetDouble(prefs::kNightLightTemperature, temperature2); |
| EXPECT_EQ(temperature2, controller->GetColorTemperature()); |
| TestCompositorsTemperature(temperature2); |
| user1_pref_service()->SetBoolean(prefs::kNightLightEnabled, false); |
| EXPECT_FALSE(controller->GetEnabled()); |
| } |
| |
| // Tests transitioning from kNone to kCustom and back to kNone schedule types. |
| TEST_F(NightLightTest, TestScheduleNoneToCustomTransition) { |
| NightLightController* controller = GetController(); |
| // Now is 6:00 PM. |
| delegate()->SetFakeNow(TimeOfDay(18 * 60)); |
| SetNightLightEnabled(false); |
| controller->SetScheduleType(NightLightController::ScheduleType::kNone); |
| // Start time is at 3:00 PM and end time is at 8:00 PM. |
| controller->SetCustomStartTime(TimeOfDay(15 * 60)); |
| controller->SetCustomEndTime(TimeOfDay(20 * 60)); |
| |
| // 15:00 18:00 20:00 |
| // <----- + ----------- + ----------- + -----> |
| // | | | |
| // start now end |
| // |
| // Even though "Now" is inside the NightLight interval, nothing should change, |
| // since the schedule type is "none". |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| |
| // Now change the schedule type to custom, NightLight should turn on |
| // immediately with a short animation duration, and the timer should be |
| // running with a delay of exactly 2 hours scheduling the end. |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(controller->GetColorTemperature()); |
| EXPECT_EQ(NightLightController::AnimationDuration::kShort, |
| controller->last_animation_duration()); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(2), |
| controller->timer()->GetCurrentDelay()); |
| |
| // If the user changes the schedule type to "none", the NightLight status |
| // should not change, but the timer should not be running. |
| controller->SetScheduleType(NightLightController::ScheduleType::kNone); |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(controller->GetColorTemperature()); |
| EXPECT_FALSE(controller->timer()->IsRunning()); |
| } |
| |
| // Tests what happens when the time now reaches the end of the NightLight |
| // interval when NightLight mode is on. |
| TEST_F(NightLightTest, TestCustomScheduleReachingEndTime) { |
| NightLightController* controller = GetController(); |
| delegate()->SetFakeNow(TimeOfDay(18 * 60)); |
| controller->SetCustomStartTime(TimeOfDay(15 * 60)); |
| controller->SetCustomEndTime(TimeOfDay(20 * 60)); |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(controller->GetColorTemperature()); |
| |
| // Simulate reaching the end time by triggering the timer's user task. Make |
| // sure that NightLight ended with a long animation. |
| // |
| // 15:00 20:00 |
| // <----- + ------------------------ + -----> |
| // | | |
| // start end & now |
| // |
| // Now is 8:00 PM. |
| delegate()->SetFakeNow(TimeOfDay(20 * 60)); |
| controller->timer()->FireNow(); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_EQ(NightLightController::AnimationDuration::kLong, |
| controller->last_animation_duration()); |
| // The timer should still be running, but now scheduling the start at 3:00 PM |
| // tomorrow which is 19 hours from "now" (8:00 PM). |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(19), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Tests that user toggles from the system menu or system settings override any |
| // status set by an automatic schedule. |
| TEST_F(NightLightTest, TestExplicitUserTogglesWhileScheduleIsActive) { |
| // Start with the below custom schedule, where NightLight is off. |
| // |
| // 15:00 20:00 23:00 |
| // <----- + ----------------- + ------------ + ----> |
| // | | | |
| // start end now |
| // |
| NightLightController* controller = GetController(); |
| delegate()->SetFakeNow(TimeOfDay(23 * 60)); |
| controller->SetCustomStartTime(TimeOfDay(15 * 60)); |
| controller->SetCustomEndTime(TimeOfDay(20 * 60)); |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| |
| // What happens if the user manually turns NightLight on while the schedule |
| // type says it should be off? |
| // User toggles either from the system menu or the System Settings toggle |
| // button must override any automatic schedule, and should be performed with |
| // the short animation duration. |
| controller->Toggle(); |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(controller->GetColorTemperature()); |
| EXPECT_EQ(NightLightController::AnimationDuration::kShort, |
| controller->last_animation_duration()); |
| // The timer should still be running, but NightLight should automatically |
| // turn off at 8:00 PM tomorrow, which is 21 hours from now (11:00 PM). |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(21), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Manually turning it back off should also be respected, and this time the |
| // start is scheduled at 3:00 PM tomorrow after 19 hours from "now" (8:00 PM). |
| controller->Toggle(); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_EQ(NightLightController::AnimationDuration::kShort, |
| controller->last_animation_duration()); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(16), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Tests that changing the custom start and end times, in such a way that |
| // shouldn't change the current status, only updates the timer but doesn't |
| // change the status. |
| TEST_F(NightLightTest, TestChangingStartTimesThatDontChangeTheStatus) { |
| // 16:00 18:00 22:00 |
| // <----- + ----------- + ----------- + -----> |
| // | | | |
| // now start end |
| // |
| NightLightController* controller = GetController(); |
| delegate()->SetFakeNow(TimeOfDay(16 * 60)); // 4:00 PM. |
| SetNightLightEnabled(false); |
| controller->SetScheduleType(NightLightController::ScheduleType::kNone); |
| controller->SetCustomStartTime(TimeOfDay(18 * 60)); // 6:00 PM. |
| controller->SetCustomEndTime(TimeOfDay(22 * 60)); // 10:00 PM. |
| |
| // Since now is outside the NightLight interval, changing the schedule type |
| // to kCustom, shouldn't affect the status. Validate the timer is running with |
| // a 2-hour delay. |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(2), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Change the start time in such a way that doesn't change the status, but |
| // despite that, confirm that schedule has been updated. |
| controller->SetCustomStartTime(TimeOfDay(19 * 60)); // 7:00 PM. |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(3), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Changing the end time in a similar fashion to the above and expect no |
| // change. |
| controller->SetCustomEndTime(TimeOfDay(23 * 60)); // 11:00 PM. |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(3), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Tests the behavior of the sunset to sunrise automatic schedule type. |
| TEST_F(NightLightTest, TestSunsetSunrise) { |
| // 16:00 18:00 20:00 22:00 5:00 |
| // <----- + ----------- + ------- + -------- + --------------- + -------> |
| // | | | | | |
| // now custom start sunset custom end sunrise |
| // |
| NightLightController* controller = GetController(); |
| delegate()->SetFakeNow(TimeOfDay(16 * 60)); // 4:00 PM. |
| delegate()->SetFakeSunset(TimeOfDay(20 * 60)); // 8:00 PM. |
| delegate()->SetFakeSunrise(TimeOfDay(5 * 60)); // 5:00 AM. |
| SetNightLightEnabled(false); |
| controller->SetScheduleType(NightLightController::ScheduleType::kNone); |
| controller->SetCustomStartTime(TimeOfDay(18 * 60)); // 6:00 PM. |
| controller->SetCustomEndTime(TimeOfDay(22 * 60)); // 10:00 PM. |
| |
| // Custom times should have no effect when the schedule type is sunset to |
| // sunrise. |
| controller->SetScheduleType( |
| NightLightController::ScheduleType::kSunsetToSunrise); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(4), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Simulate reaching sunset. |
| delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM. |
| controller->timer()->FireNow(); |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(controller->GetColorTemperature()); |
| EXPECT_EQ(NightLightController::AnimationDuration::kLong, |
| controller->last_animation_duration()); |
| // Timer is running scheduling the end at sunrise. |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(9), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Simulate reaching sunrise. |
| delegate()->SetFakeNow(TimeOfDay(5 * 60)); // Now is 5:00 AM. |
| controller->timer()->FireNow(); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_EQ(NightLightController::AnimationDuration::kLong, |
| controller->last_animation_duration()); |
| // Timer is running scheduling the start at the next sunset. |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(15), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Tests the behavior of the sunset to sunrise automatic schedule type when the |
| // client sets the geoposition. |
| TEST_F(NightLightTest, TestSunsetSunriseGeoposition) { |
| // Position 1 sunset and sunrise times. |
| // |
| // 16:00 20:00 4:00 |
| // <----- + --------- + ---------------- + -------> |
| // | | | |
| // now sunset sunrise |
| // |
| NightLightController* controller = GetController(); |
| delegate()->SetFakeNow(TimeOfDay(16 * 60)); // 4:00 PM. |
| controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New( |
| kFakePosition1_Latitude, kFakePosition1_Longitude)); |
| |
| // Expect that timer is running and the start is scheduled after 4 hours. |
| controller->SetScheduleType( |
| NightLightController::ScheduleType::kSunsetToSunrise); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(4), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Simulate reaching sunset. |
| delegate()->SetFakeNow(TimeOfDay(20 * 60)); // Now is 8:00 PM. |
| controller->timer()->FireNow(); |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(controller->GetColorTemperature()); |
| EXPECT_EQ(NightLightController::AnimationDuration::kLong, |
| controller->last_animation_duration()); |
| // Timer is running scheduling the end at sunrise. |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(8), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Now simulate user changing position. |
| // Position 2 sunset and sunrise times. |
| // |
| // 17:00 20:00 3:00 |
| // <----- + --------- + ---------------- + -------> |
| // | | | |
| // sunset now sunrise |
| // |
| controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New( |
| kFakePosition2_Latitude, kFakePosition2_Longitude)); |
| |
| // Expect that the scheduled end delay has been updated, and the status hasn't |
| // changed. |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(controller->GetColorTemperature()); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(7), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Simulate reaching sunrise. |
| delegate()->SetFakeNow(TimeOfDay(3 * 60)); // Now is 5:00 AM. |
| controller->timer()->FireNow(); |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_EQ(NightLightController::AnimationDuration::kLong, |
| controller->last_animation_duration()); |
| // Timer is running scheduling the start at the next sunset. |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| EXPECT_EQ(base::TimeDelta::FromHours(14), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Tests the behavior when there is no valid geoposition for example due to lack |
| // of connectivity. |
| TEST_F(NightLightTest, AbsentValidGeoposition) { |
| NightLightController* controller = GetController(); |
| ASSERT_FALSE(delegate()->HasGeoposition()); |
| |
| // Initially, no values are stored in either of the two users' prefs. |
| ASSERT_FALSE( |
| user1_pref_service()->HasPrefPath(prefs::kNightLightCachedLatitude)); |
| ASSERT_FALSE( |
| user1_pref_service()->HasPrefPath(prefs::kNightLightCachedLongitude)); |
| ASSERT_FALSE( |
| user2_pref_service()->HasPrefPath(prefs::kNightLightCachedLatitude)); |
| ASSERT_FALSE( |
| user2_pref_service()->HasPrefPath(prefs::kNightLightCachedLongitude)); |
| |
| // Store fake geoposition 2 in user 2's prefs. |
| user2_pref_service()->SetDouble(prefs::kNightLightCachedLatitude, |
| kFakePosition2_Latitude); |
| user2_pref_service()->SetDouble(prefs::kNightLightCachedLongitude, |
| kFakePosition2_Longitude); |
| |
| // Switch to user 2 and expect that the delegate now has a geoposition, but |
| // the controller knows that it's from a cached value. |
| SwitchActiveUser(kUser2Email); |
| EXPECT_TRUE(delegate()->HasGeoposition()); |
| EXPECT_TRUE(controller->is_current_geoposition_from_cache()); |
| const TimeOfDay kSunset2{kFakePosition2_SunsetOffset}; |
| const TimeOfDay kSunrise2{kFakePosition2_SunriseOffset}; |
| EXPECT_EQ(delegate()->GetSunsetTime(), kSunset2.ToTimeToday()); |
| EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise2.ToTimeToday()); |
| |
| // Store fake geoposition 1 in user 1's prefs. |
| user1_pref_service()->SetDouble(prefs::kNightLightCachedLatitude, |
| kFakePosition1_Latitude); |
| user1_pref_service()->SetDouble(prefs::kNightLightCachedLongitude, |
| kFakePosition1_Longitude); |
| |
| // Switching to user 1 should ignore the current geoposition since it's |
| // a cached value from user 2's prefs rather than a newly-updated value. |
| // User 1's cached values should be loaded. |
| SwitchActiveUser(kUser1Email); |
| EXPECT_TRUE(delegate()->HasGeoposition()); |
| EXPECT_TRUE(controller->is_current_geoposition_from_cache()); |
| const TimeOfDay kSunset1{kFakePosition1_SunsetOffset}; |
| const TimeOfDay kSunrise1{kFakePosition1_SunriseOffset}; |
| EXPECT_EQ(delegate()->GetSunsetTime(), kSunset1.ToTimeToday()); |
| EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise1.ToTimeToday()); |
| |
| // Now simulate receiving a geoposition update of fake geoposition 2. |
| controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New( |
| kFakePosition2_Latitude, kFakePosition2_Longitude)); |
| EXPECT_TRUE(delegate()->HasGeoposition()); |
| EXPECT_FALSE(controller->is_current_geoposition_from_cache()); |
| EXPECT_EQ(delegate()->GetSunsetTime(), kSunset2.ToTimeToday()); |
| EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise2.ToTimeToday()); |
| |
| // Update user 2's prefs with fake geoposition 1. |
| user2_pref_service()->SetDouble(prefs::kNightLightCachedLatitude, |
| kFakePosition1_Latitude); |
| user2_pref_service()->SetDouble(prefs::kNightLightCachedLongitude, |
| kFakePosition1_Longitude); |
| |
| // Now switching to user 2 should completely ignore their cached geopsoition, |
| // since from now on we have a valid newly-retrieved value. |
| SwitchActiveUser(kUser2Email); |
| EXPECT_TRUE(delegate()->HasGeoposition()); |
| EXPECT_FALSE(controller->is_current_geoposition_from_cache()); |
| EXPECT_EQ(delegate()->GetSunsetTime(), kSunset2.ToTimeToday()); |
| EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise2.ToTimeToday()); |
| |
| // Clear all cached geoposition prefs for all users, just to make sure getting |
| // a new geoposition with persist it for all users not just the active one. |
| user1_pref_service()->ClearPref(prefs::kNightLightCachedLatitude); |
| user1_pref_service()->ClearPref(prefs::kNightLightCachedLongitude); |
| user2_pref_service()->ClearPref(prefs::kNightLightCachedLatitude); |
| user2_pref_service()->ClearPref(prefs::kNightLightCachedLongitude); |
| |
| // Now simulate receiving a geoposition update of fake geoposition 1. |
| controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New( |
| kFakePosition1_Latitude, kFakePosition1_Longitude)); |
| EXPECT_TRUE(delegate()->HasGeoposition()); |
| EXPECT_FALSE(controller->is_current_geoposition_from_cache()); |
| EXPECT_EQ(delegate()->GetSunsetTime(), kSunset1.ToTimeToday()); |
| EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise1.ToTimeToday()); |
| EXPECT_EQ(kFakePosition1_Latitude, |
| user1_pref_service()->GetDouble(prefs::kNightLightCachedLatitude)); |
| EXPECT_EQ(kFakePosition1_Longitude, |
| user1_pref_service()->GetDouble(prefs::kNightLightCachedLongitude)); |
| EXPECT_EQ(kFakePosition1_Latitude, |
| user2_pref_service()->GetDouble(prefs::kNightLightCachedLatitude)); |
| EXPECT_EQ(kFakePosition1_Longitude, |
| user2_pref_service()->GetDouble(prefs::kNightLightCachedLongitude)); |
| } |
| |
| // Tests that on device resume from sleep, the NightLight status is updated |
| // correctly if the time has changed meanwhile. |
| TEST_F(NightLightTest, TestCustomScheduleOnResume) { |
| NightLightController* controller = GetController(); |
| // Now is 4:00 PM. |
| delegate()->SetFakeNow(TimeOfDay(16 * 60)); |
| SetNightLightEnabled(false); |
| // Start time is at 6:00 PM and end time is at 10:00 PM. NightLight should be |
| // off. |
| // 16:00 18:00 22:00 |
| // <----- + ----------- + ----------- + -----> |
| // | | | |
| // now start end |
| // |
| controller->SetColorTemperature(0.4f); |
| controller->SetCustomStartTime(TimeOfDay(18 * 60)); |
| controller->SetCustomEndTime(TimeOfDay(22 * 60)); |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| // NightLight should start in 2 hours. |
| EXPECT_EQ(base::TimeDelta::FromHours(2), |
| controller->timer()->GetCurrentDelay()); |
| |
| // Now simulate that the device was suspended for 3 hours, and the time now |
| // is 7:00 PM when the devices was resumed. Expect that NightLight turns on. |
| delegate()->SetFakeNow(TimeOfDay(19 * 60)); |
| controller->SuspendDone(base::TimeDelta::Max()); |
| |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.4f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| // NightLight should end in 3 hours. |
| EXPECT_EQ(base::TimeDelta::FromHours(3), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // The following tests ensure that the NightLight schedule is correctly |
| // refreshed when the start and end times are inverted (i.e. the "start time" as |
| // a time of day today is in the future with respect to the "end time" also as a |
| // time of day today). |
| // |
| // Case 1: "Now" is less than both "end" and "start". |
| TEST_F(NightLightTest, TestCustomScheduleInvertedStartAndEndTimesCase1) { |
| NightLightController* controller = GetController(); |
| // Now is 4:00 AM. |
| delegate()->SetFakeNow(TimeOfDay(4 * 60)); |
| SetNightLightEnabled(false); |
| // Start time is at 9:00 PM and end time is at 6:00 AM. "Now" is less than |
| // both. NightLight should be on. |
| // 4:00 6:00 21:00 |
| // <----- + ----------- + ----------- + -----> |
| // | | | |
| // now end start |
| // |
| controller->SetColorTemperature(0.4f); |
| controller->SetCustomStartTime(TimeOfDay(21 * 60)); |
| controller->SetCustomEndTime(TimeOfDay(6 * 60)); |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.4f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| // NightLight should end in two hours. |
| EXPECT_EQ(base::TimeDelta::FromHours(2), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Case 2: "Now" is between "end" and "start". |
| TEST_F(NightLightTest, TestCustomScheduleInvertedStartAndEndTimesCase2) { |
| NightLightController* controller = GetController(); |
| // Now is 6:00 AM. |
| delegate()->SetFakeNow(TimeOfDay(6 * 60)); |
| SetNightLightEnabled(false); |
| // Start time is at 9:00 PM and end time is at 4:00 AM. "Now" is between both. |
| // NightLight should be off. |
| // 4:00 6:00 21:00 |
| // <----- + ----------- + ----------- + -----> |
| // | | | |
| // end now start |
| // |
| controller->SetColorTemperature(0.4f); |
| controller->SetCustomStartTime(TimeOfDay(21 * 60)); |
| controller->SetCustomEndTime(TimeOfDay(4 * 60)); |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| |
| EXPECT_FALSE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| // NightLight should start in 15 hours. |
| EXPECT_EQ(base::TimeDelta::FromHours(15), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Case 3: "Now" is greater than both "start" and "end". |
| TEST_F(NightLightTest, TestCustomScheduleInvertedStartAndEndTimesCase3) { |
| NightLightController* controller = GetController(); |
| // Now is 11:00 PM. |
| delegate()->SetFakeNow(TimeOfDay(23 * 60)); |
| SetNightLightEnabled(false); |
| // Start time is at 9:00 PM and end time is at 4:00 AM. "Now" is greater than |
| // both. NightLight should be on. |
| // 4:00 21:00 23:00 |
| // <----- + ----------- + ----------- + -----> |
| // | | | |
| // end start now |
| // |
| controller->SetColorTemperature(0.4f); |
| controller->SetCustomStartTime(TimeOfDay(21 * 60)); |
| controller->SetCustomEndTime(TimeOfDay(4 * 60)); |
| controller->SetScheduleType(NightLightController::ScheduleType::kCustom); |
| |
| EXPECT_TRUE(controller->GetEnabled()); |
| TestCompositorsTemperature(0.4f); |
| EXPECT_TRUE(controller->timer()->IsRunning()); |
| // NightLight should end in 5 hours. |
| EXPECT_EQ(base::TimeDelta::FromHours(5), |
| controller->timer()->GetCurrentDelay()); |
| } |
| |
| // Fixture for testing behavior of Night Light when displays support hardware |
| // CRTC matrices. |
| class NightLightCrtcTest : public NightLightTest { |
| public: |
| NightLightCrtcTest() |
| : logger_(std::make_unique<display::test::ActionLogger>()) {} |
| ~NightLightCrtcTest() override = default; |
| |
| static constexpr gfx::Size kDisplaySize{1024, 768}; |
| static constexpr int64_t kId1 = 123; |
| static constexpr int64_t kId2 = 456; |
| |
| // NightLightTest: |
| void SetUp() override { |
| NightLightTest::SetUp(); |
| |
| native_display_delegate_ = |
| new display::test::TestNativeDisplayDelegate(logger_.get()); |
| display_manager()->configurator()->SetDelegateForTesting( |
| std::unique_ptr<display::NativeDisplayDelegate>( |
| native_display_delegate_)); |
| display_change_observer_ = |
| std::make_unique<display::DisplayChangeObserver>(display_manager()); |
| test_api_ = std::make_unique<display::DisplayConfigurator::TestApi>( |
| display_manager()->configurator()); |
| } |
| |
| void TearDown() override { |
| // DisplayChangeObserver access InputDeviceManager in its destructor, so |
| // destroy it first. |
| display_change_observer_ = nullptr; |
| NightLightTest::TearDown(); |
| } |
| |
| struct TestSnapshotParams { |
| bool has_ctm_support; |
| bool correction_in_linear_space; |
| }; |
| |
| // Builds two displays snapshots into |owned_snapshots_| and return a list of |
| // unowned pointers to them. |snapshot_params| should contain exactly 2 |
| // elements that correspond to capabilities of both displays. |
| std::vector<display::DisplaySnapshot*> BuildAndGetDisplaySnapshots( |
| const std::vector<TestSnapshotParams>& snapshot_params) { |
| DCHECK_EQ(2u, snapshot_params.size()); |
| owned_snapshots_.clear(); |
| owned_snapshots_.emplace_back( |
| display::FakeDisplaySnapshot::Builder() |
| .SetId(kId1) |
| .SetNativeMode(kDisplaySize) |
| .SetCurrentMode(kDisplaySize) |
| .SetHasColorCorrectionMatrix(snapshot_params[0].has_ctm_support) |
| .SetColorCorrectionInLinearSpace( |
| snapshot_params[0].correction_in_linear_space) |
| .SetType(display::DISPLAY_CONNECTION_TYPE_INTERNAL) |
| .Build()); |
| owned_snapshots_.back()->set_origin({0, 0}); |
| owned_snapshots_.emplace_back( |
| display::FakeDisplaySnapshot::Builder() |
| .SetId(kId2) |
| .SetNativeMode(kDisplaySize) |
| .SetCurrentMode(kDisplaySize) |
| .SetHasColorCorrectionMatrix(snapshot_params[1].has_ctm_support) |
| .SetColorCorrectionInLinearSpace( |
| snapshot_params[1].correction_in_linear_space) |
| .Build()); |
| owned_snapshots_.back()->set_origin({1030, 0}); |
| std::vector<display::DisplaySnapshot*> outputs = { |
| owned_snapshots_[0].get(), owned_snapshots_[1].get()}; |
| return outputs; |
| } |
| |
| // Updates the display configurator and display manager with the given list of |
| // display snapshots. |
| void UpdateDisplays(const std::vector<display::DisplaySnapshot*>& outputs) { |
| native_display_delegate_->set_outputs(outputs); |
| display_manager()->configurator()->OnConfigurationChanged(); |
| EXPECT_TRUE(test_api_->TriggerConfigureTimeout()); |
| display_change_observer_->GetStateForDisplayIds(outputs); |
| display_change_observer_->OnDisplayModeChanged(outputs); |
| } |
| |
| // Returns true if the software cursor is turned on. |
| bool IsCursorCompositingEnabled() const { |
| return Shell::Get() |
| ->window_tree_host_manager() |
| ->cursor_window_controller() |
| ->ShouldEnableCursorCompositing(); |
| } |
| |
| std::string GetLoggerActionsAndClear() { |
| return logger_->GetActionsAndClear(); |
| } |
| |
| bool VerifyCrtcMatrix(int64_t display_id, |
| float temperature, |
| bool in_linear_gamma_space, |
| const std::string& logger_actions_string) const { |
| if (in_linear_gamma_space) |
| temperature = NightLightController::GetNonLinearTemperature(temperature); |
| constexpr float kRedScale = 1.0f; |
| const float blue_scale = |
| NightLightController::BlueColorScaleFromTemperature(temperature); |
| const float green_scale = |
| NightLightController::GreenColorScaleFromTemperature( |
| temperature, in_linear_gamma_space); |
| std::stringstream pattern_stream; |
| pattern_stream << "*set_color_matrix(id=" << display_id |
| << ",ctm[0]=" << kRedScale << "*ctm[4]=" << green_scale |
| << "*ctm[8]=" << blue_scale << "*)*"; |
| return base::MatchPattern(logger_actions_string, pattern_stream.str()); |
| } |
| |
| private: |
| std::unique_ptr<display::test::ActionLogger> logger_; |
| // Not owned. |
| display::test::TestNativeDisplayDelegate* native_display_delegate_; |
| std::unique_ptr<display::DisplayChangeObserver> display_change_observer_; |
| std::unique_ptr<display::DisplayConfigurator::TestApi> test_api_; |
| |
| std::vector<std::unique_ptr<display::DisplaySnapshot>> owned_snapshots_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NightLightCrtcTest); |
| }; |
| |
| // static |
| constexpr gfx::Size NightLightCrtcTest::kDisplaySize; |
| |
| // All displays support CRTC matrices. |
| TEST_F(NightLightCrtcTest, TestAllDisplaysSupportCrtcMatrix) { |
| // Create two displays with both having support for CRTC matrices. |
| std::vector<display::DisplaySnapshot*> outputs = |
| BuildAndGetDisplaySnapshots({{true, true}, {true, true}}); |
| UpdateDisplays(outputs); |
| |
| EXPECT_EQ(2u, display_manager()->GetNumDisplays()); |
| ASSERT_EQ(2u, RootWindowController::root_window_controllers().size()); |
| |
| // Turn on Night Light: |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(true); |
| float temperature = 0.2f; |
| GetLoggerActionsAndClear(); |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| |
| // Since both displays support CRTC matrices, no compositor matrix should be |
| // set (i.e. compositor matrix is identity which corresponds to the zero |
| // temperature). |
| TestCompositorsTemperature(0.0f); |
| // Hence software cursor should not be used. |
| EXPECT_FALSE(IsCursorCompositingEnabled()); |
| // Verify correct matrix has been set on both crtcs. |
| std::string logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId1, temperature, true, logger_actions)); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId2, temperature, true, logger_actions)); |
| |
| // Setting a new temperature is applied. |
| temperature = 0.65f; |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_FALSE(IsCursorCompositingEnabled()); |
| logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId1, temperature, true, logger_actions)); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId2, temperature, true, logger_actions)); |
| |
| // Test the cursor compositing behavior when Night Light is on (and doesn't |
| // require the software cursor) while other accessibility settings that affect |
| // the cursor are toggled. |
| for (const auto* const pref : {prefs::kAccessibilityLargeCursorEnabled, |
| prefs::kAccessibilityHighContrastEnabled}) { |
| user1_pref_service()->SetBoolean(pref, true); |
| Shell::Get()->UpdateCursorCompositingEnabled(); |
| EXPECT_TRUE(IsCursorCompositingEnabled()); |
| |
| // Disabling the accessibility feature should revert back to the hardware |
| // cursor. |
| user1_pref_service()->SetBoolean(pref, false); |
| Shell::Get()->UpdateCursorCompositingEnabled(); |
| EXPECT_FALSE(IsCursorCompositingEnabled()); |
| } |
| } |
| |
| // All displays support CRTC matrices in the compressed gamma space. |
| TEST_F(NightLightCrtcTest, |
| TestAllDisplaysSupportCrtcMatrixCompressedGammaSpace) { |
| // Create two displays with both having support for CRTC matrices that are |
| // applied in the compressed gamma space. |
| std::vector<display::DisplaySnapshot*> outputs = |
| BuildAndGetDisplaySnapshots({{true, false}, {true, false}}); |
| UpdateDisplays(outputs); |
| |
| EXPECT_EQ(2u, display_manager()->GetNumDisplays()); |
| ASSERT_EQ(2u, RootWindowController::root_window_controllers().size()); |
| |
| // Turn on Night Light: |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(true); |
| float temperature = 0.2f; |
| GetLoggerActionsAndClear(); |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| |
| // Since both displays support CRTC matrices, no compositor matrix should be |
| // set (i.e. compositor matrix is identity which corresponds to the zero |
| // temperature). |
| TestCompositorsTemperature(0.0f); |
| // Hence software cursor should not be used. |
| EXPECT_FALSE(IsCursorCompositingEnabled()); |
| // Verify compressed gamma space matrix has been set on both crtcs. |
| std::string logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId1, temperature, false, logger_actions)); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId2, temperature, false, logger_actions)); |
| |
| // Setting a new temperature is applied. |
| temperature = 0.65f; |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_FALSE(IsCursorCompositingEnabled()); |
| logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId1, temperature, false, logger_actions)); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId2, temperature, false, logger_actions)); |
| } |
| |
| // One display supports CRTC matrix and the other doesn't. |
| TEST_F(NightLightCrtcTest, TestMixedCrtcMatrixSupport) { |
| std::vector<display::DisplaySnapshot*> outputs = |
| BuildAndGetDisplaySnapshots({{true, true}, {false, false}}); |
| UpdateDisplays(outputs); |
| |
| EXPECT_EQ(2u, display_manager()->GetNumDisplays()); |
| ASSERT_EQ(2u, RootWindowController::root_window_controllers().size()); |
| |
| // Turn on Night Light: |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(true); |
| const float temperature = 0.2f; |
| GetLoggerActionsAndClear(); |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| |
| // The first display supports CRTC matrix, so its compositor has identity |
| // matrix. |
| TestDisplayCompositorTemperature(kId1, 0.0f); |
| // However, the second display doesn't support CRTC matrix, Night Light is |
| // using the compositor matrix on this display. |
| TestDisplayCompositorTemperature(kId2, temperature); |
| // With mixed CTRC support, software cursor must be on. |
| EXPECT_TRUE(IsCursorCompositingEnabled()); |
| // Verify correct matrix has been set on both crtcs. |
| const std::string logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId1, temperature, true, logger_actions)); |
| EXPECT_FALSE(VerifyCrtcMatrix(kId2, temperature, false, logger_actions)); |
| } |
| |
| // All displays don't support CRTC matrices. |
| TEST_F(NightLightCrtcTest, TestNoCrtcMatrixSupport) { |
| std::vector<display::DisplaySnapshot*> outputs = |
| BuildAndGetDisplaySnapshots({{false, false}, {false, false}}); |
| UpdateDisplays(outputs); |
| |
| EXPECT_EQ(2u, display_manager()->GetNumDisplays()); |
| ASSERT_EQ(2u, RootWindowController::root_window_controllers().size()); |
| |
| // Turn on Night Light: |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(true); |
| const float temperature = 0.2f; |
| GetLoggerActionsAndClear(); |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| |
| // Compositor matrices are used on both displays. |
| TestCompositorsTemperature(temperature); |
| // With no CTRC support, software cursor must be on. |
| EXPECT_TRUE(IsCursorCompositingEnabled()); |
| // No CRTC matrices have been set. |
| const std::string logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_FALSE(VerifyCrtcMatrix(kId1, temperature, false, logger_actions)); |
| EXPECT_FALSE(VerifyCrtcMatrix(kId2, temperature, false, logger_actions)); |
| } |
| |
| // Tests that switching CRTC matrix support on while Night Light is enabled |
| // doesn't result in the matrix being applied twice. |
| TEST_F(NightLightCrtcTest, TestNoDoubleNightLightEffect) { |
| std::vector<display::DisplaySnapshot*> outputs = |
| BuildAndGetDisplaySnapshots({{false, false}, {false, false}}); |
| UpdateDisplays(outputs); |
| |
| EXPECT_EQ(2u, display_manager()->GetNumDisplays()); |
| ASSERT_EQ(2u, RootWindowController::root_window_controllers().size()); |
| |
| // Turn on Night Light: |
| NightLightController* controller = GetController(); |
| SetNightLightEnabled(true); |
| const float temperature = 0.2f; |
| GetLoggerActionsAndClear(); |
| controller->SetColorTemperature(temperature); |
| EXPECT_EQ(temperature, controller->GetColorTemperature()); |
| |
| // Compositor matrices are used on both displays. |
| TestCompositorsTemperature(temperature); |
| // With no CTRC support, software cursor must be on. |
| EXPECT_TRUE(IsCursorCompositingEnabled()); |
| // No CRTC matrices have been set. |
| std::string logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_FALSE(VerifyCrtcMatrix(kId1, temperature, false, logger_actions)); |
| EXPECT_FALSE(VerifyCrtcMatrix(kId2, temperature, false, logger_actions)); |
| |
| // Simulate that the two displays suddenly became able to support CRTC matrix. |
| // This shouldn't happen in practice, but we noticed multiple times on resume |
| // from suspend, or after the display turned on after it was off as a result |
| // of no user activity, we noticed that the display can get into a transient |
| // state where it is wrongly believed to have no CTM matrix capability, then |
| // later corrected. When this happened, we noticed that the Night Light effect |
| // is applied twice; once via the CRTC CTM matrix, and another via the |
| // compositor matrix. When this happens, we need to assert that the compositor |
| // matrix is set to identity, and the cursor compositing is updated correctly. |
| // TODO(afakhry): Investigate the root cause of this https://crbug.com/844067. |
| std::vector<display::DisplaySnapshot*> outputs2 = |
| BuildAndGetDisplaySnapshots({{true, true}, {true, true}}); |
| UpdateDisplays(outputs2); |
| TestCompositorsTemperature(0.0f); |
| EXPECT_FALSE(IsCursorCompositingEnabled()); |
| logger_actions = GetLoggerActionsAndClear(); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId1, temperature, true, logger_actions)); |
| EXPECT_TRUE(VerifyCrtcMatrix(kId2, temperature, true, logger_actions)); |
| } |
| |
| } // namespace |
| |
| } // namespace ash |