| // 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/lock_screen_action/lock_screen_note_display_state_handler.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/accessibility/test_accessibility_controller_client.h" |
| #include "ash/public/cpp/ash_switches.h" |
| #include "ash/public/mojom/tray_action.mojom.h" |
| #include "ash/shell.h" |
| #include "ash/system/power/power_button_controller.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/tray_action/test_tray_action_client.h" |
| #include "ash/tray_action/tray_action.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/run_loop.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "chromeos/dbus/power/fake_power_manager_client.h" |
| #include "chromeos/dbus/power_manager/backlight.pb.h" |
| #include "ui/events/devices/device_data_manager_test_api.h" |
| #include "ui/events/devices/stylus_state.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr double kVisibleBrightnessPercent = 10; |
| |
| class TestPowerManagerObserver : public chromeos::PowerManagerClient::Observer { |
| public: |
| TestPowerManagerObserver() |
| : power_manager_(chromeos::FakePowerManagerClient::Get()) { |
| scoped_observer_.Add(power_manager_); |
| power_manager_->set_user_activity_callback(base::BindRepeating( |
| &TestPowerManagerObserver::OnUserActivity, base::Unretained(this))); |
| } |
| ~TestPowerManagerObserver() override { |
| power_manager_->set_user_activity_callback(base::RepeatingClosure()); |
| } |
| |
| const std::vector<double>& brightness_changes() const { |
| return brightness_changes_; |
| } |
| |
| void ClearBrightnessChanges() { brightness_changes_.clear(); } |
| |
| // chromeos::PowerManagerClient::Observer: |
| void ScreenBrightnessChanged( |
| const power_manager::BacklightBrightnessChange& change) override { |
| brightness_changes_.push_back(change.percent()); |
| } |
| |
| void OnUserActivity() { |
| if (!power_manager_->backlights_forced_off() && |
| power_manager_->screen_brightness_percent() == 0) { |
| brightness_changes_.push_back(kVisibleBrightnessPercent); |
| } |
| } |
| |
| private: |
| chromeos::FakePowerManagerClient* power_manager_; |
| std::vector<double> brightness_changes_; |
| |
| ScopedObserver<chromeos::PowerManagerClient, |
| chromeos::PowerManagerClient::Observer> |
| scoped_observer_{this}; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestPowerManagerObserver); |
| }; |
| |
| } // namespace |
| |
| class LockScreenNoteDisplayStateHandlerTest : public AshTestBase { |
| public: |
| LockScreenNoteDisplayStateHandlerTest() = default; |
| ~LockScreenNoteDisplayStateHandlerTest() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kAshEnableTabletMode); |
| |
| AshTestBase::SetUp(); |
| |
| power_manager::SetBacklightBrightnessRequest request; |
| request.set_percent(kVisibleBrightnessPercent); |
| power_manager_client()->SetScreenBrightness(request); |
| |
| power_manager_observer_ = std::make_unique<TestPowerManagerObserver>(); |
| |
| BlockUserSession(BLOCKED_BY_LOCK_SCREEN); |
| InitializeTabletPowerButtonState(); |
| |
| Shell::Get()->tray_action()->SetClient( |
| tray_action_client_.CreateRemoteAndBind(), |
| mojom::TrayActionState::kAvailable); |
| Shell::Get()->tray_action()->FlushMojoForTesting(); |
| // Run the loop so the lock screen note display state handler picks up |
| // initial screen brightness. |
| base::RunLoop().RunUntilIdle(); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| // Advance the tick clock so it's not close to the null clock value. |
| tick_clock_.Advance(base::TimeDelta::FromMilliseconds(10000)); |
| } |
| void TearDown() override { |
| power_manager_observer_.reset(); |
| AshTestBase::TearDown(); |
| } |
| |
| bool LaunchTimeoutRunning() { |
| return Shell::Get() |
| ->tray_action() |
| ->lock_screen_note_display_state_handler_for_test() |
| ->launch_timer_for_test() |
| ->IsRunning(); |
| } |
| |
| bool TriggerNoteLaunchTimeout() { |
| base::OneShotTimer* timer = |
| Shell::Get() |
| ->tray_action() |
| ->lock_screen_note_display_state_handler_for_test() |
| ->launch_timer_for_test(); |
| if (!timer->IsRunning()) |
| return false; |
| |
| timer->FireNow(); |
| return true; |
| } |
| |
| void TurnScreenOffForUserInactivity() { |
| power_manager_client()->set_screen_brightness_percent(0); |
| power_manager::BacklightBrightnessChange change; |
| change.set_percent(0.0); |
| change.set_cause(power_manager::BacklightBrightnessChange_Cause_OTHER); |
| power_manager_client()->SendScreenBrightnessChanged(change); |
| power_manager_observer_->ClearBrightnessChanges(); |
| } |
| |
| void SimulatePowerButtonPress() { |
| power_manager_client()->SendPowerButtonEvent(true, tick_clock_.NowTicks()); |
| tick_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); |
| power_manager_client()->SendPowerButtonEvent(false, tick_clock_.NowTicks()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| testing::AssertionResult SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin expected_note_origin) { |
| Shell::Get()->tray_action()->FlushMojoForTesting(); |
| |
| if (tray_action_client_.note_origins().size() != 1) { |
| return testing::AssertionFailure() |
| << "Expected a single note action request, found " |
| << tray_action_client_.note_origins().size(); |
| } |
| |
| if (tray_action_client_.note_origins()[0] != expected_note_origin) { |
| return testing::AssertionFailure() |
| << "Unexpected note request origin: " |
| << tray_action_client_.note_origins()[0] |
| << "; expected: " << expected_note_origin; |
| } |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kLaunching); |
| return testing::AssertionSuccess(); |
| } |
| |
| protected: |
| std::unique_ptr<TestPowerManagerObserver> power_manager_observer_; |
| TestTrayActionClient tray_action_client_; |
| |
| private: |
| void InitializeTabletPowerButtonState() { |
| // Initialize the tablet power button controller only if the tablet mode |
| // switch is set. |
| Shell::Get()->power_button_controller()->OnGetSwitchStates( |
| chromeos::PowerManagerClient::SwitchStates{ |
| chromeos::PowerManagerClient::LidState::OPEN, |
| chromeos::PowerManagerClient::TabletMode::ON}); |
| Shell::Get()->power_button_controller()->OnTabletModeStarted(); |
| } |
| |
| base::SimpleTestTickClock tick_clock_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LockScreenNoteDisplayStateHandlerTest); |
| }; |
| |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, EjectWhenScreenOn) { |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE(power_manager_observer_->brightness_changes().empty()); |
| |
| ASSERT_TRUE(SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin::kStylusEject)); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE(power_manager_observer_->brightness_changes().empty()); |
| |
| ASSERT_FALSE(LaunchTimeoutRunning()); |
| } |
| |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, EjectWhenScreenOff) { |
| TurnScreenOffForUserInactivity(); |
| |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({0.0}), |
| power_manager_observer_->brightness_changes()); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| ASSERT_TRUE(SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin::kStylusEject)); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({kVisibleBrightnessPercent}), |
| power_manager_observer_->brightness_changes()); |
| |
| ASSERT_FALSE(LaunchTimeoutRunning()); |
| } |
| |
| // TODO(crbug.com/1002488): Test is flaky. |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, |
| DISABLED_EjectWhenScreenOffAndNoteNotAvailable) { |
| TurnScreenOffForUserInactivity(); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kNotAvailable); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE(power_manager_observer_->brightness_changes().empty()); |
| |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Styluls eject is expected to turn the screen on due to user activity. |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({kVisibleBrightnessPercent}), |
| power_manager_observer_->brightness_changes()); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| EXPECT_TRUE(tray_action_client_.note_origins().empty()); |
| |
| // Verify that later (unrelated) note launch does not affect power manager |
| // state. |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kLaunching); |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE(power_manager_observer_->brightness_changes().empty()); |
| |
| ASSERT_FALSE(LaunchTimeoutRunning()); |
| } |
| |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, TurnScreenOnWhenAppLaunchFails) { |
| TurnScreenOffForUserInactivity(); |
| |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({0.0}), |
| power_manager_observer_->brightness_changes()); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| ASSERT_TRUE(SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin::kStylusEject)); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kAvailable); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({kVisibleBrightnessPercent}), |
| power_manager_observer_->brightness_changes()); |
| |
| ASSERT_FALSE(LaunchTimeoutRunning()); |
| } |
| |
| // Tests stylus eject when the backlights was turned off by a power button |
| // getting pressed - in particular, it makes sure that power button display |
| // controller cancelling backlights forced off on stylus eject does not happen |
| // before lock screen note display state handler requests backlights to be |
| // forced off (i.e. that backlights are continuosly kept forced off). |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, EjectWhileScreenForcedOff) { |
| SimulatePowerButtonPress(); |
| |
| ASSERT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({0.0}), |
| power_manager_observer_->brightness_changes()); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE(power_manager_observer_->brightness_changes().empty()); |
| |
| ASSERT_TRUE(SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin::kStylusEject)); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({kVisibleBrightnessPercent}), |
| power_manager_observer_->brightness_changes()); |
| |
| ASSERT_FALSE(LaunchTimeoutRunning()); |
| } |
| |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, DisplayNotTurnedOffIndefinitely) { |
| TurnScreenOffForUserInactivity(); |
| |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| ASSERT_TRUE(SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin::kStylusEject)); |
| |
| EXPECT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({0.0}), |
| power_manager_observer_->brightness_changes()); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| ASSERT_TRUE(TriggerNoteLaunchTimeout()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(std::vector<double>({kVisibleBrightnessPercent}), |
| power_manager_observer_->brightness_changes()); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE(power_manager_observer_->brightness_changes().empty()); |
| |
| ASSERT_FALSE(LaunchTimeoutRunning()); |
| } |
| |
| // Test that verifies that lock screen note request is delayed until screen is |
| // turned off if stylus eject happens soon after power button is pressed, while |
| // display configuration to off is still in progress. |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, |
| StylusEjectWhileForcingDisplayOff) { |
| power_manager_client() |
| ->set_enqueue_brightness_changes_on_backlights_forced_off(true); |
| |
| SimulatePowerButtonPress(); |
| EXPECT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE(power_manager_observer_->brightness_changes().empty()); |
| EXPECT_EQ(1u, |
| power_manager_client()->pending_screen_brightness_changes().size()); |
| |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify that there are no note requests when the screen brightness due to |
| // backlights being forced off is still being updated. |
| Shell::Get()->tray_action()->FlushMojoForTesting(); |
| EXPECT_TRUE(tray_action_client_.note_origins().empty()); |
| |
| // Apply screen brightness set by forcing backlights off, |
| EXPECT_EQ(1u, |
| power_manager_client()->pending_screen_brightness_changes().size()); |
| ASSERT_TRUE(power_manager_client()->ApplyPendingScreenBrightnessChange()); |
| |
| EXPECT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_TRUE( |
| power_manager_client()->pending_screen_brightness_changes().empty()); |
| EXPECT_EQ(std::vector<double>({0.0}), |
| power_manager_observer_->brightness_changes()); |
| power_manager_observer_->ClearBrightnessChanges(); |
| |
| ASSERT_TRUE(SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin::kStylusEject)); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(power_manager_client()->backlights_forced_off()); |
| ASSERT_TRUE(power_manager_client()->ApplyPendingScreenBrightnessChange()); |
| EXPECT_EQ(std::vector<double>({kVisibleBrightnessPercent}), |
| power_manager_observer_->brightness_changes()); |
| |
| ASSERT_FALSE(LaunchTimeoutRunning()); |
| } |
| |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, ScreenA11yAlerts) { |
| TestAccessibilityControllerClient a11y_client; |
| SimulatePowerButtonPress(); |
| ASSERT_TRUE(power_manager_client()->backlights_forced_off()); |
| EXPECT_EQ(AccessibilityAlert::SCREEN_OFF, a11y_client.last_a11y_alert()); |
| |
| ui::DeviceDataManagerTestApi devices_test_api; |
| devices_test_api.NotifyObserversStylusStateChanged(ui::StylusState::REMOVED); |
| base::RunLoop().RunUntilIdle(); |
| |
| ASSERT_TRUE(SimulateNoteLaunchStartedIfNoteActionRequested( |
| mojom::LockScreenNoteOrigin::kStylusEject)); |
| EXPECT_TRUE(power_manager_client()->backlights_forced_off()); |
| |
| // Screen ON alert is delayed until the screen is turned on after lock screen |
| // note launch. |
| EXPECT_EQ(AccessibilityAlert::SCREEN_OFF, a11y_client.last_a11y_alert()); |
| |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify that screen on a11y alert has been sent. |
| EXPECT_EQ(AccessibilityAlert::SCREEN_ON, a11y_client.last_a11y_alert()); |
| } |
| |
| TEST_F(LockScreenNoteDisplayStateHandlerTest, |
| PowerButtonPressClosesLockScreenNote) { |
| Shell::Get()->tray_action()->UpdateLockScreenNoteState( |
| mojom::TrayActionState::kActive); |
| |
| SimulatePowerButtonPress(); |
| |
| Shell::Get()->tray_action()->FlushMojoForTesting(); |
| EXPECT_EQ(std::vector<mojom::CloseLockScreenNoteReason>( |
| {mojom::CloseLockScreenNoteReason::kScreenDimmed}), |
| tray_action_client_.close_note_reasons()); |
| } |
| |
| } // namespace ash |