| // Copyright 2014 The Chromium OS 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 "power_manager/powerd/policy/input_device_controller.h" |
| |
| #include <cstdarg> |
| |
| #include <gtest/gtest.h> |
| |
| #include "power_manager/common/fake_prefs.h" |
| #include "power_manager/common/power_constants.h" |
| #include "power_manager/powerd/policy/backlight_controller_stub.h" |
| #include "power_manager/powerd/system/acpi_wakeup_helper_stub.h" |
| #include "power_manager/powerd/system/cros_ec_helper_stub.h" |
| #include "power_manager/powerd/system/udev_stub.h" |
| #include "power_manager/proto_bindings/backlight.pb.h" |
| |
| namespace power_manager { |
| namespace policy { |
| |
| namespace { |
| // An artificial syspath for tests. |
| const char kSyspath0[] = "/sys/devices/test/0"; |
| |
| // Create aliases for lengthy constant names. |
| const char* const kTagInhibit = InputDeviceController::kTagInhibit; |
| const char* const kTagUsableWhenDocked = |
| InputDeviceController::kTagUsableWhenDocked; |
| const char* const kTagUsableWhenDisplayOff = |
| InputDeviceController::kTagUsableWhenDisplayOff; |
| const char* const kTagUsableWhenLaptop = |
| InputDeviceController::kTagUsableWhenLaptop; |
| const char* const kTagUsableWhenTablet = |
| InputDeviceController::kTagUsableWhenTablet; |
| const char* const kTagWakeup = InputDeviceController::kTagWakeup; |
| const char* const kTagWakeupWhenLaptop = |
| InputDeviceController::kTagWakeupWhenLaptop; |
| const char* const kTagWakeupOnlyWhenUsable = |
| InputDeviceController::kTagWakeupOnlyWhenUsable; |
| const char* const kTagWakeupDisabled = |
| InputDeviceController::kTagWakeupDisabled; |
| const char* const kEnabled = InputDeviceController::kWakeupEnabled; |
| const char* const kDisabled = InputDeviceController::kWakeupDisabled; |
| const char* const kInhibited = InputDeviceController::kInhibited; |
| const char* const kTPAD = InputDeviceController::kTPAD; |
| const char* const kTSCR = InputDeviceController::kTSCR; |
| |
| } // namespace |
| |
| class InputDeviceControllerTest : public ::testing::Test { |
| public: |
| InputDeviceControllerTest() = default; |
| ~InputDeviceControllerTest() override = default; |
| |
| protected: |
| std::string GetSysattr(const std::string& syspath, |
| const std::string& sysattr) { |
| std::string value; |
| if (!udev_.GetSysattr(syspath, sysattr, &value)) |
| return "(error)"; |
| return value; |
| } |
| |
| bool GetAcpiWakeup(const std::string& acpi_name) { |
| bool value = false; |
| if (!acpi_wakeup_helper_.GetWakeupEnabled(acpi_name, &value)) { |
| ADD_FAILURE() << "Expected ACPI wakeup for " << acpi_name |
| << " to be defined"; |
| } |
| return value; |
| } |
| |
| // Adds a device at |syspath| with one or more udev tags. The tag list must be |
| // null-terminated. |
| void AddDeviceWithTags(const std::string& syspath, const char* tag, ...) { |
| udev_.SetSysattr(syspath, kPowerWakeup, kDisabled); |
| |
| std::string tags; |
| va_list arg_list; |
| va_start(arg_list, tag); |
| while (tag) { |
| if (!tags.empty()) |
| tags += " "; |
| tags += tag; |
| tag = va_arg(arg_list, const char*); |
| } |
| va_end(arg_list); |
| |
| udev_.TaggedDeviceChanged(syspath, base::FilePath(syspath), tags); |
| } |
| |
| void InitInputDeviceController() { |
| input_device_controller_.Init(&backlight_controller_, &udev_, |
| &acpi_wakeup_helper_, &ec_helper_, |
| initial_lid_state_, initial_tablet_mode_, |
| initial_display_mode_, &prefs_); |
| } |
| |
| policy::BacklightControllerStub backlight_controller_; |
| system::UdevStub udev_; |
| system::AcpiWakeupHelperStub acpi_wakeup_helper_; |
| system::CrosEcHelperStub ec_helper_; |
| FakePrefs prefs_; |
| |
| LidState initial_lid_state_ = LidState::OPEN; |
| TabletMode initial_tablet_mode_ = TabletMode::OFF; |
| DisplayMode initial_display_mode_ = DisplayMode::NORMAL; |
| |
| InputDeviceController input_device_controller_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(InputDeviceControllerTest); |
| }; |
| |
| TEST_F(InputDeviceControllerTest, ConfigureWakeupOnInit) { |
| AddDeviceWithTags(kSyspath0, kTagWakeup, nullptr); |
| |
| EXPECT_EQ(kDisabled, GetSysattr(kSyspath0, kPowerWakeup)); |
| InitInputDeviceController(); |
| EXPECT_EQ(kEnabled, GetSysattr(kSyspath0, kPowerWakeup)); |
| EXPECT_TRUE(GetAcpiWakeup(kTPAD)); |
| EXPECT_FALSE(GetAcpiWakeup(kTSCR)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, ConfigureWakeupOnAdd) { |
| InitInputDeviceController(); |
| |
| // The device starts out with wakeup disabled, but should get configured by |
| // InputDeviceController right away. |
| AddDeviceWithTags(kSyspath0, kTagWakeup, nullptr); |
| EXPECT_EQ(kEnabled, GetSysattr(kSyspath0, kPowerWakeup)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, DisableWakeupWhenClosed) { |
| AddDeviceWithTags(kSyspath0, kTagWakeup, kTagWakeupOnlyWhenUsable, |
| kTagUsableWhenLaptop, nullptr); |
| InitInputDeviceController(); |
| |
| // In laptop mode, wakeup should be enabled. |
| EXPECT_EQ(kEnabled, GetSysattr(kSyspath0, kPowerWakeup)); |
| EXPECT_TRUE(GetAcpiWakeup("TPAD")); |
| |
| // When the lid is closed, wakeup should be disabled. |
| input_device_controller_.SetLidState(LidState::CLOSED); |
| EXPECT_EQ(kDisabled, GetSysattr(kSyspath0, kPowerWakeup)); |
| EXPECT_FALSE(GetAcpiWakeup(kTPAD)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, PermanentlyDisableWakeup) { |
| AddDeviceWithTags(kSyspath0, kTagWakeup, kTagWakeupDisabled, nullptr); |
| |
| // Simulate a device that has wakeup enabled initially. |
| udev_.SetSysattr(kSyspath0, kPowerWakeup, kEnabled); |
| InitInputDeviceController(); |
| EXPECT_EQ(kDisabled, GetSysattr(kSyspath0, kPowerWakeup)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, ConfigureInhibit) { |
| AddDeviceWithTags(kSyspath0, kTagInhibit, kTagUsableWhenLaptop, nullptr); |
| InitInputDeviceController(); |
| |
| // In laptop mode, inhibit should be off. |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When the lid is closed, inhibit should be on. |
| input_device_controller_.SetLidState(LidState::CLOSED); |
| EXPECT_EQ("1", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When the lid is open, inhibit should be off again. |
| input_device_controller_.SetLidState(LidState::OPEN); |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, InhibitDocking) { |
| AddDeviceWithTags(kSyspath0, kTagInhibit, kTagUsableWhenLaptop, |
| kTagUsableWhenDocked, nullptr); |
| initial_display_mode_ = DisplayMode::PRESENTATION; |
| InitInputDeviceController(); |
| |
| // In laptop mode, inhibit should be off. |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When the lid is closed, inhibit should remain off. |
| input_device_controller_.SetLidState(LidState::CLOSED); |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When the lid is open, inhibit should still be off. |
| input_device_controller_.SetLidState(LidState::OPEN); |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, SetDisplayModeExternalInput) { |
| AddDeviceWithTags(kSyspath0, kTagInhibit, kTagUsableWhenLaptop, |
| kTagUsableWhenDocked, nullptr); |
| initial_lid_state_ = LidState::CLOSED; |
| InitInputDeviceController(); |
| |
| // When the lid is closed with no external display, external input devices |
| // should be inhibited. |
| EXPECT_EQ("1", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When an external display is attached, device should be un-inhibited. |
| input_device_controller_.SetDisplayMode(DisplayMode::PRESENTATION); |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When external display goes away, input should be inhibited again. |
| input_device_controller_.SetDisplayMode(DisplayMode::NORMAL); |
| EXPECT_EQ("1", GetSysattr(kSyspath0, kInhibited)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, SetDisplayModeInternalInput) { |
| AddDeviceWithTags(kSyspath0, kTagInhibit, kTagUsableWhenLaptop, nullptr); |
| InitInputDeviceController(); |
| |
| // Devices that are only usable when in laptop mode should not be inhibited |
| // while the lid is open. |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When an external display is attached, device should remain uninhibited. |
| input_device_controller_.SetDisplayMode(DisplayMode::PRESENTATION); |
| EXPECT_EQ("0", GetSysattr(kSyspath0, kInhibited)); |
| |
| // When the lid is closed, internal input should be inhibited regardless |
| // of display mode. |
| input_device_controller_.SetLidState(LidState::CLOSED); |
| EXPECT_EQ("1", GetSysattr(kSyspath0, kInhibited)); |
| |
| input_device_controller_.SetDisplayMode(DisplayMode::NORMAL); |
| EXPECT_EQ("1", GetSysattr(kSyspath0, kInhibited)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, AllowEcWakeupAsTabletWhenDisplayOff) { |
| InitInputDeviceController(); |
| |
| // Start in presentation mode at full brightness. |
| input_device_controller_.SetDisplayMode(DisplayMode::PRESENTATION); |
| backlight_controller_.NotifyObservers( |
| 100.0, BacklightBrightnessChange_Cause_USER_REQUEST); |
| |
| // EC wakeups should be inhibited in tablet mode while backlight is on. |
| EXPECT_FALSE(ec_helper_.IsWakeupAsTabletAllowed()); |
| |
| // Automated display off should not trigger a mode change. |
| backlight_controller_.NotifyObservers( |
| 0.0, BacklightBrightnessChange_Cause_USER_INACTIVITY); |
| EXPECT_FALSE(ec_helper_.IsWakeupAsTabletAllowed()); |
| |
| // ...but manual should. |
| backlight_controller_.NotifyObservers( |
| 0.0, BacklightBrightnessChange_Cause_USER_REQUEST); |
| EXPECT_TRUE(ec_helper_.IsWakeupAsTabletAllowed()); |
| |
| // Leaving presentation mode should disallow it. |
| input_device_controller_.SetDisplayMode(DisplayMode::NORMAL); |
| EXPECT_FALSE(ec_helper_.IsWakeupAsTabletAllowed()); |
| input_device_controller_.SetDisplayMode(DisplayMode::PRESENTATION); |
| EXPECT_TRUE(ec_helper_.IsWakeupAsTabletAllowed()); |
| |
| // As should raising the brightness, even if automatic. |
| backlight_controller_.NotifyObservers(10.0, |
| BacklightBrightnessChange_Cause_OTHER); |
| EXPECT_FALSE(ec_helper_.IsWakeupAsTabletAllowed()); |
| } |
| |
| TEST_F(InputDeviceControllerTest, HandleTabletMode) { |
| const char kKeyboardSyspath[] = "/sys/devices/keyboard/0"; |
| const char kTouchscreenSyspath[] = "/sys/devices/touchscreen/0"; |
| AddDeviceWithTags(kKeyboardSyspath, kTagInhibit, kTagWakeup, |
| kTagWakeupOnlyWhenUsable, kTagUsableWhenLaptop, |
| kTagUsableWhenDisplayOff, nullptr); |
| AddDeviceWithTags(kTouchscreenSyspath, kTagInhibit, kTagWakeup, |
| kTagWakeupOnlyWhenUsable, kTagUsableWhenLaptop, |
| kTagUsableWhenTablet, nullptr); |
| initial_tablet_mode_ = TabletMode::ON; |
| |
| // While in tablet mode, the keyboard should be inhibited with wakeup |
| // disabled. |
| InitInputDeviceController(); |
| EXPECT_EQ("1", GetSysattr(kKeyboardSyspath, kInhibited)); |
| EXPECT_EQ(kDisabled, GetSysattr(kKeyboardSyspath, kPowerWakeup)); |
| EXPECT_EQ("0", GetSysattr(kTouchscreenSyspath, kInhibited)); |
| EXPECT_EQ(kEnabled, GetSysattr(kTouchscreenSyspath, kPowerWakeup)); |
| |
| // Switching to laptop mode should uninhibit the keyboard and permit wakeups. |
| input_device_controller_.SetTabletMode(TabletMode::OFF); |
| EXPECT_EQ("0", GetSysattr(kKeyboardSyspath, kInhibited)); |
| EXPECT_EQ(kEnabled, GetSysattr(kKeyboardSyspath, kPowerWakeup)); |
| EXPECT_EQ("0", GetSysattr(kTouchscreenSyspath, kInhibited)); |
| EXPECT_EQ(kEnabled, GetSysattr(kTouchscreenSyspath, kPowerWakeup)); |
| |
| // Lid-closed mode should take precedence over tablet mode. (See b/119287727) |
| input_device_controller_.SetTabletMode(TabletMode::ON); |
| input_device_controller_.SetLidState(LidState::CLOSED); |
| EXPECT_EQ("1", GetSysattr(kKeyboardSyspath, kInhibited)); |
| EXPECT_EQ(kDisabled, GetSysattr(kKeyboardSyspath, kPowerWakeup)); |
| EXPECT_EQ("1", GetSysattr(kTouchscreenSyspath, kInhibited)); |
| EXPECT_EQ(kDisabled, GetSysattr(kTouchscreenSyspath, kPowerWakeup)); |
| |
| // Display-off mode should take precedence over tablet mode. |
| input_device_controller_.SetDisplayMode(DisplayMode::PRESENTATION); |
| input_device_controller_.SetLidState(LidState::OPEN); |
| input_device_controller_.SetTabletMode(TabletMode::ON); |
| input_device_controller_.OnBrightnessChange( |
| 0.0, BacklightBrightnessChange_Cause_USER_REQUEST, |
| &backlight_controller_); |
| EXPECT_EQ("0", GetSysattr(kKeyboardSyspath, kInhibited)); |
| EXPECT_EQ(kEnabled, GetSysattr(kKeyboardSyspath, kPowerWakeup)); |
| EXPECT_EQ("1", GetSysattr(kTouchscreenSyspath, kInhibited)); |
| EXPECT_EQ(kDisabled, GetSysattr(kTouchscreenSyspath, kPowerWakeup)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, UsableWithoutWakeup) { |
| // Add a keyboard device that should remain usable while in tablet mode (say, |
| // because it also produces power button events: http://crbug.com/703691) but |
| // that should only wake the device while in laptop mode. |
| const char kKeyboardSyspath[] = "/sys/devices/keyboard/0"; |
| AddDeviceWithTags(kKeyboardSyspath, kTagInhibit, kTagUsableWhenLaptop, |
| kTagUsableWhenTablet, kTagWakeup, kTagWakeupWhenLaptop, |
| nullptr); |
| |
| initial_tablet_mode_ = TabletMode::OFF; |
| InitInputDeviceController(); |
| EXPECT_EQ("0", GetSysattr(kKeyboardSyspath, kInhibited)); |
| EXPECT_EQ(kEnabled, GetSysattr(kKeyboardSyspath, kPowerWakeup)); |
| |
| input_device_controller_.SetTabletMode(TabletMode::ON); |
| EXPECT_EQ("0", GetSysattr(kKeyboardSyspath, kInhibited)); |
| EXPECT_EQ(kDisabled, GetSysattr(kKeyboardSyspath, kPowerWakeup)); |
| |
| input_device_controller_.SetTabletMode(TabletMode::OFF); |
| EXPECT_EQ("0", GetSysattr(kKeyboardSyspath, kInhibited)); |
| EXPECT_EQ(kEnabled, GetSysattr(kKeyboardSyspath, kPowerWakeup)); |
| } |
| |
| TEST_F(InputDeviceControllerTest, InitWithoutBacklightController) { |
| // Init with null backlight controller shouldn't crash. |
| input_device_controller_.Init( |
| nullptr, &udev_, &acpi_wakeup_helper_, &ec_helper_, initial_lid_state_, |
| initial_tablet_mode_, initial_display_mode_, &prefs_); |
| } |
| |
| } // namespace policy |
| } // namespace power_manager |