power: Suspend instead of shutting down for TPM counter.
Add a tpm_counter_suspend_threshold powerd pref. If the
TPM's dictionary-attack counter reaches this value, idle and
lid-closed "shut down" actions will be overridden to
suspend instead.
Also give the "power" user permission to call cryptohomed's
GetTpmStatus D-Bus methoed.
BUG=chromium:462428
TEST=added unit tests; also manually tested by setting the
pref and watching /var/log/power_manager/powerd.LATEST
CQ-DEPEND=CL:254570
Change-Id: I3c5944ea5faaf78eedc074a1292f4d8124194d45
Previous-Reviewed-on: https://chromium-review.googlesource.com/254670
(cherry picked from commit 696bd299934f40c706f2ac378a6995336efbbf90)
Reviewed-on: https://chromium-review.googlesource.com/259280
Reviewed-by: Dan Erat <derat@chromium.org>
Reviewed-by: Luigi Semenzato <semenzato@chromium.org>
Commit-Queue: Luigi Semenzato <semenzato@chromium.org>
Tested-by: Luigi Semenzato <semenzato@chromium.org>
diff --git a/cryptohome/cryptohome.cc b/cryptohome/cryptohome.cc
index c983c8e..c7a7151 100644
--- a/cryptohome/cryptohome.cc
+++ b/cryptohome/cryptohome.cc
@@ -1571,7 +1571,7 @@
action.c_str())) {
cryptohome::GetTpmStatusRequest request;
cryptohome::BaseReply reply;
- if (!MakeProtoDBusCall("GetTpmStatus",
+ if (!MakeProtoDBusCall(cryptohome::kCryptohomeGetTpmStatus,
DBUS_METHOD(get_tpm_status),
DBUS_METHOD(get_tpm_status_async),
cl, &proxy, request, &reply)) {
diff --git a/cryptohome/etc/Cryptohome.conf b/cryptohome/etc/Cryptohome.conf
index ddb848a..ba57f0e 100644
--- a/cryptohome/etc/Cryptohome.conf
+++ b/cryptohome/etc/Cryptohome.conf
@@ -258,6 +258,11 @@
send_interface="org.chromium.CryptohomeInterface"
send_member="FlushAndSignBootAttributes"/>
</policy>
+ <policy user="power">
+ <allow send_destination="org.chromium.Cryptohome"
+ send_interface="org.chromium.CryptohomeInterface"
+ send_member="GetTpmStatus"/>
+ </policy>
<policy context="default">
<deny send_destination="org.chromium.Cryptohome" />
</policy>
diff --git a/power_manager/common/power_constants.cc b/power_manager/common/power_constants.cc
index 5ddbd18..928438a 100644
--- a/power_manager/common/power_constants.cc
+++ b/power_manager/common/power_constants.cc
@@ -72,6 +72,7 @@
const char kMosysEventlogPref[] = "mosys_eventlog";
const char kCheckActiveVTPref[] = "check_active_vt";
const char kUseCrasPref[] = "use_cras";
+const char kTpmCounterSuspendThresholdPref[] = "tpm_counter_suspend_threshold";
// Miscellaneous constants.
const char kReadWritePrefsDir[] = "/var/lib/power_manager";
diff --git a/power_manager/common/power_constants.h b/power_manager/common/power_constants.h
index fde20a9..17f8d5d 100644
--- a/power_manager/common/power_constants.h
+++ b/power_manager/common/power_constants.h
@@ -173,6 +173,11 @@
// to mute audio when suspending.
extern const char kUseCrasPref[];
+// Integer TPM dictionary-attack counter value at or above which the system will
+// suspend instead of shutting down in response to idle or lid-close (see
+// http://crbug.com/462428). Set to 0 to disable querying the TPM.
+extern const char kTpmCounterSuspendThresholdPref[];
+
// Miscellaneous constants.
// Default directories where read/write and read-only powerd preference files
diff --git a/power_manager/default_prefs/tpm_counter_suspend_threshold b/power_manager/default_prefs/tpm_counter_suspend_threshold
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/power_manager/default_prefs/tpm_counter_suspend_threshold
@@ -0,0 +1 @@
+0
diff --git a/power_manager/powerd/daemon.cc b/power_manager/powerd/daemon.cc
index 2526b11..7d14f49 100644
--- a/power_manager/powerd/daemon.cc
+++ b/power_manager/powerd/daemon.cc
@@ -17,6 +17,7 @@
#include <chromeos/dbus/service_constants.h>
#include <metrics/metrics_library.h>
+#include "cryptohome/proto_bindings/rpc.pb.h"
#include "power_manager/common/dbus_sender.h"
#include "power_manager/common/metrics_sender.h"
#include "power_manager/common/power_constants.h"
@@ -83,6 +84,7 @@
// processes.
const int kSessionManagerDBusTimeoutMs = 3000;
const int kUpdateEngineDBusTimeoutMs = 3000;
+const int kCryptohomedDBusTimeoutMs = 2 * 60 * 1000; // Two minutes.
// If we go from a dark resume directly to a full resume several devices will be
// left in an awkward state. This won't be a problem once selective resume is
@@ -320,6 +322,7 @@
session_manager_dbus_proxy_(NULL),
cras_dbus_proxy_(NULL),
update_engine_dbus_proxy_(NULL),
+ cryptohomed_dbus_proxy_(NULL),
state_controller_delegate_(new StateControllerDelegate(this)),
dbus_sender_(new DBusSender),
display_watcher_(new system::DisplayWatcher),
@@ -344,7 +347,8 @@
lock_vt_before_suspend_(false),
log_suspend_with_mosys_eventlog_(false),
can_safely_exit_dark_resume_(
- base::PathExists(base::FilePath(kPMTestDelayPath))) {
+ base::PathExists(base::FilePath(kPMTestDelayPath))),
+ weak_ptr_factory_(this) {
scoped_ptr<MetricsLibrary> metrics_lib(new MetricsLibrary);
metrics_lib->Init();
metrics_sender_.reset(new MetricsSender(metrics_lib.Pass()));
@@ -789,20 +793,21 @@
dbus::ObjectPath(chromeos::kLibCrosServicePath));
chrome_dbus_proxy_->WaitForServiceToBeAvailable(
base::Bind(&Daemon::HandleChromeAvailableOrRestarted,
- base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()));
session_manager_dbus_proxy_ = bus_->GetObjectProxy(
login_manager::kSessionManagerServiceName,
dbus::ObjectPath(login_manager::kSessionManagerServicePath));
session_manager_dbus_proxy_->WaitForServiceToBeAvailable(
base::Bind(&Daemon::HandleSessionManagerAvailableOrRestarted,
- base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()));
session_manager_dbus_proxy_->ConnectToSignal(
login_manager::kSessionManagerInterface,
login_manager::kSessionStateChangedSignal,
base::Bind(&Daemon::HandleSessionStateChangedSignal,
- base::Unretained(this)),
- base::Bind(&Daemon::HandleDBusSignalConnected, base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&Daemon::HandleDBusSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
if (audio_client_) {
cras_dbus_proxy_ = bus_->GetObjectProxy(
@@ -810,39 +815,54 @@
dbus::ObjectPath(cras::kCrasServicePath));
cras_dbus_proxy_->WaitForServiceToBeAvailable(
base::Bind(&Daemon::HandleCrasAvailableOrRestarted,
- base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()));
cras_dbus_proxy_->ConnectToSignal(
cras::kCrasControlInterface,
cras::kNodesChanged,
base::Bind(&Daemon::HandleCrasNodesChangedSignal,
- base::Unretained(this)),
- base::Bind(&Daemon::HandleDBusSignalConnected, base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&Daemon::HandleDBusSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
cras_dbus_proxy_->ConnectToSignal(
cras::kCrasControlInterface,
cras::kActiveOutputNodeChanged,
base::Bind(&Daemon::HandleCrasActiveOutputNodeChangedSignal,
- base::Unretained(this)),
- base::Bind(&Daemon::HandleDBusSignalConnected, base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&Daemon::HandleDBusSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
cras_dbus_proxy_->ConnectToSignal(
cras::kCrasControlInterface,
cras::kNumberOfActiveStreamsChanged,
base::Bind(&Daemon::HandleCrasNumberOfActiveStreamsChanged,
- base::Unretained(this)),
- base::Bind(&Daemon::HandleDBusSignalConnected, base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&Daemon::HandleDBusSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
}
update_engine_dbus_proxy_ = bus_->GetObjectProxy(
update_engine::kUpdateEngineServiceName,
dbus::ObjectPath(update_engine::kUpdateEngineServicePath));
update_engine_dbus_proxy_->WaitForServiceToBeAvailable(
- base::Bind(&Daemon::HandleUpdateEngineAvailableOrRestarted,
- base::Unretained(this)));
+ base::Bind(&Daemon::HandleUpdateEngineAvailable,
+ weak_ptr_factory_.GetWeakPtr()));
update_engine_dbus_proxy_->ConnectToSignal(
update_engine::kUpdateEngineInterface,
update_engine::kStatusUpdate,
base::Bind(&Daemon::HandleUpdateEngineStatusUpdateSignal,
- base::Unretained(this)),
- base::Bind(&Daemon::HandleDBusSignalConnected, base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&Daemon::HandleDBusSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ int64 tpm_threshold = 0;
+ prefs_->GetInt64(kTpmCounterSuspendThresholdPref, &tpm_threshold);
+ if (tpm_threshold > 0) {
+ cryptohomed_dbus_proxy_ = bus_->GetObjectProxy(
+ cryptohome::kCryptohomeServiceName,
+ dbus::ObjectPath(cryptohome::kCryptohomeServicePath));
+ cryptohomed_dbus_proxy_->WaitForServiceToBeAvailable(
+ base::Bind(&Daemon::HandleCryptohomedAvailable,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
powerd_dbus_object_ = bus_->GetExportedObject(
dbus::ObjectPath(kPowerManagerServicePath));
@@ -921,8 +941,10 @@
dbus::ObjectProxy* proxy = bus_->GetObjectProxy(
kBusServiceName, dbus::ObjectPath(kBusServicePath));
proxy->ConnectToSignal(kBusInterface, kNameOwnerChangedSignal,
- base::Bind(&Daemon::HandleDBusNameOwnerChanged, base::Unretained(this)),
- base::Bind(&Daemon::HandleDBusSignalConnected, base::Unretained(this)));
+ base::Bind(&Daemon::HandleDBusNameOwnerChanged,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&Daemon::HandleDBusSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
dbus_sender_->Init(powerd_dbus_object_, kPowerManagerInterface);
}
@@ -970,7 +992,7 @@
audio_client_->LoadInitialState();
}
-void Daemon::HandleUpdateEngineAvailableOrRestarted(bool available) {
+void Daemon::HandleUpdateEngineAvailable(bool available) {
if (!available) {
LOG(ERROR) << "Failed waiting for update engine to become available";
return;
@@ -997,6 +1019,23 @@
OnUpdateOperation(operation);
}
+void Daemon::HandleCryptohomedAvailable(bool available) {
+ if (!available) {
+ LOG(ERROR) << "Failed waiting for cryptohomed to become available";
+ return;
+ }
+ if (cryptohomed_dbus_proxy_) {
+ dbus::MethodCall method_call(cryptohome::kCryptohomeInterface,
+ cryptohome::kCryptohomeGetTpmStatus);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendProtoAsArrayOfBytes(cryptohome::GetTpmStatusRequest());
+ cryptohomed_dbus_proxy_->CallMethod(
+ &method_call, kCryptohomedDBusTimeoutMs,
+ base::Bind(&Daemon::HandleGetTpmStatusResponse,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+}
+
void Daemon::HandleDBusNameOwnerChanged(dbus::Signal* signal) {
dbus::MessageReader reader(signal);
std::string name, old_owner, new_owner;
@@ -1076,6 +1115,38 @@
audio_client_->UpdateNumActiveStreams();
}
+void Daemon::HandleGetTpmStatusResponse(dbus::Response* response) {
+ if (!response) {
+ LOG(ERROR) << cryptohome::kCryptohomeGetTpmStatus << " call failed";
+ return;
+ }
+
+ cryptohome::BaseReply base_reply;
+ dbus::MessageReader reader(response);
+ if (!reader.PopArrayOfBytesAsProto(&base_reply)) {
+ LOG(ERROR) << "Unable to parse " << cryptohome::kCryptohomeGetTpmStatus
+ << "response";
+ return;
+ }
+ if (base_reply.has_error()) {
+ LOG(ERROR) << cryptohome::kCryptohomeGetTpmStatus << " response contains "
+ << "error code " << base_reply.error();
+ return;
+ }
+ if (!base_reply.HasExtension(cryptohome::GetTpmStatusReply::reply)) {
+ LOG(ERROR) << cryptohome::kCryptohomeGetTpmStatus << " response doesn't "
+ << "contain nested reply";
+ return;
+ }
+
+ cryptohome::GetTpmStatusReply tpm_reply =
+ base_reply.GetExtension(cryptohome::GetTpmStatusReply::reply);
+ LOG(INFO) << "Received " << cryptohome::kCryptohomeGetTpmStatus
+ << " response with dictionary attack count "
+ << tpm_reply.dictionary_attack_counter();
+ state_controller_->HandleTpmStatus(tpm_reply.dictionary_attack_counter());
+}
+
scoped_ptr<dbus::Response> Daemon::HandleRequestShutdownMethod(
dbus::MethodCall* method_call) {
LOG(INFO) << "Got " << kRequestShutdownMethod << " message from "
@@ -1372,7 +1443,8 @@
if (!retry_shutdown_for_flashrom_timer_.IsRunning()) {
retry_shutdown_for_flashrom_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(kRetryShutdownForFlashromSec),
- base::Bind(&Daemon::ShutDown, base::Unretained(this), mode, reason));
+ base::Bind(&Daemon::ShutDown, weak_ptr_factory_.GetWeakPtr(), mode,
+ reason));
}
return;
}
diff --git a/power_manager/powerd/daemon.h b/power_manager/powerd/daemon.h
index 8363b11..29429c8 100644
--- a/power_manager/powerd/daemon.h
+++ b/power_manager/powerd/daemon.h
@@ -14,6 +14,7 @@
#include <base/files/file_path.h>
#include <base/macros.h>
#include <base/memory/scoped_ptr.h>
+#include <base/memory/weak_ptr.h>
#include <base/time/time.h>
#include <base/timer/timer.h>
#include <dbus/bus.h>
@@ -156,7 +157,11 @@
void HandleChromeAvailableOrRestarted(bool available);
void HandleSessionManagerAvailableOrRestarted(bool available);
void HandleCrasAvailableOrRestarted(bool available);
- void HandleUpdateEngineAvailableOrRestarted(bool available);
+
+ // Handles other D-Bus services just becoming initially available (i.e.
+ // restarts are ignored).
+ void HandleUpdateEngineAvailable(bool available);
+ void HandleCryptohomedAvailable(bool available);
// Handles changes to D-Bus name ownership.
void HandleDBusNameOwnerChanged(dbus::Signal* signal);
@@ -177,6 +182,7 @@
void HandleCrasNodesChangedSignal(dbus::Signal* signal);
void HandleCrasActiveOutputNodeChangedSignal(dbus::Signal* signal);
void HandleCrasNumberOfActiveStreamsChanged(dbus::Signal* signal);
+ void HandleGetTpmStatusResponse(dbus::Response* response);
scoped_ptr<dbus::Response> HandleRequestShutdownMethod(
dbus::MethodCall* method_call);
scoped_ptr<dbus::Response> HandleRequestRestartMethod(
@@ -239,6 +245,8 @@
dbus::ObjectProxy* session_manager_dbus_proxy_; // weak; owned by |bus_|
dbus::ObjectProxy* cras_dbus_proxy_; // weak; owned by |bus_| and may be NULL
dbus::ObjectProxy* update_engine_dbus_proxy_; // weak; owned by |bus_|
+ // May be null if the TPM status is not needed.
+ dbus::ObjectProxy* cryptohomed_dbus_proxy_; // weak; owned by |bus_|
scoped_ptr<StateControllerDelegate> state_controller_delegate_;
scoped_ptr<MetricsSender> metrics_sender_;
@@ -300,6 +308,10 @@
// resumed.
bool can_safely_exit_dark_resume_;
+ // Must come last so that weak pointers will be invalidated before other
+ // members are destroyed.
+ base::WeakPtrFactory<Daemon> weak_ptr_factory_;
+
DISALLOW_COPY_AND_ASSIGN(Daemon);
};
diff --git a/power_manager/powerd/policy/state_controller.cc b/power_manager/powerd/policy/state_controller.cc
index 0c9548d..69e39f7 100644
--- a/power_manager/powerd/policy/state_controller.cc
+++ b/power_manager/powerd/policy/state_controller.cc
@@ -236,6 +236,8 @@
disable_idle_suspend_(false),
allow_docked_mode_(false),
ignore_external_policy_(false),
+ tpm_dictionary_attack_count_(0),
+ tpm_dictionary_attack_suspend_threshold_(0),
audio_is_active_(false),
idle_action_(DO_NOTHING),
lid_closed_action_(DO_NOTHING),
@@ -419,6 +421,11 @@
UpdateState();
}
+void StateController::HandleTpmStatus(int dictionary_attack_count) {
+ tpm_dictionary_attack_count_ = dictionary_attack_count;
+ UpdateSettingsAndState();
+}
+
void StateController::OnPrefChanged(const std::string& pref_name) {
CHECK(initialized_);
if (pref_name == kDisableIdleSuspendPref ||
@@ -608,6 +615,10 @@
prefs_->GetBool(kIgnoreExternalPolicyPref, &ignore_external_policy_);
prefs_->GetBool(kAllowDockedModePref, &allow_docked_mode_);
+ int64_t tpm_threshold = 0;
+ prefs_->GetInt64(kTpmCounterSuspendThresholdPref, &tpm_threshold);
+ tpm_dictionary_attack_suspend_threshold_ = static_cast<int>(tpm_threshold);
+
CHECK(GetMillisecondPref(prefs_, kPluggedSuspendMsPref,
&pref_ac_delays_.idle));
CHECK(GetMillisecondPref(prefs_, kPluggedOffMsPref,
@@ -703,6 +714,21 @@
if (allow_docked_mode_ && presenting)
lid_closed_action_ = DO_NOTHING;
+ // Override the idle and lid-closed actions to suspend instead of shutting
+ // down if the TPM dictionary-attack counter is high.
+ if (tpm_dictionary_attack_suspend_threshold_ > 0 &&
+ tpm_dictionary_attack_count_ >=
+ tpm_dictionary_attack_suspend_threshold_) {
+ LOG(WARNING) << "TPM dictionary attack count is "
+ << tpm_dictionary_attack_count_ << " (threshold is "
+ << tpm_dictionary_attack_suspend_threshold_ << "); "
+ << "overriding actions to suspend instead of shutting down";
+ if (idle_action_ == SHUT_DOWN)
+ idle_action_ = SUSPEND;
+ if (lid_closed_action_ == SHUT_DOWN)
+ lid_closed_action_ = SUSPEND;
+ }
+
// If the idle or lid-closed actions changed, make sure that we perform
// the new actions in the event that the system is already idle or the
// lid is already closed.
diff --git a/power_manager/powerd/policy/state_controller.h b/power_manager/powerd/policy/state_controller.h
index 24d5a18..e756869 100644
--- a/power_manager/powerd/policy/state_controller.h
+++ b/power_manager/powerd/policy/state_controller.h
@@ -150,9 +150,12 @@
void HandleUserActivity();
void HandleVideoActivity();
- // Handle audio activity starting or stopping.
+ // Handles audio activity starting or stopping.
void HandleAudioStateChange(bool active);
+ // Handles updates to the TPM status.
+ void HandleTpmStatus(int dictionary_attack_count);
+
// PrefsInterface::Observer implementation:
void OnPrefChanged(const std::string& pref_name) override;
@@ -367,6 +370,14 @@
// Should |policy_| be ignored? Used by tests and developers.
bool ignore_external_policy_;
+ // TPM dictionary-attack counter value.
+ int tpm_dictionary_attack_count_;
+
+ // |tpm_dictionary_attack_count_| value at or above which the system will
+ // suspend instead of shutting down in some cases (see
+ // http://crbug.com/462428), or 0 if disabled.
+ int tpm_dictionary_attack_suspend_threshold_;
+
base::TimeTicks last_user_activity_time_;
base::TimeTicks last_video_activity_time_;
diff --git a/power_manager/powerd/policy/state_controller_unittest.cc b/power_manager/powerd/policy/state_controller_unittest.cc
index ba481ec..73df480 100644
--- a/power_manager/powerd/policy/state_controller_unittest.cc
+++ b/power_manager/powerd/policy/state_controller_unittest.cc
@@ -1529,5 +1529,64 @@
ASSERT_TRUE(AdvanceTimeAndTriggerTimeout(kWarningDelay));
}
+// Tests that idle and lid-closed "shut down" actions are overridden to instead
+// suspend when the TPM dictionary-attack count is high.
+TEST_F(StateControllerTest, SuspendInsteadOfShuttingDownForTpmCounter) {
+ const base::TimeDelta kIdleDelay = base::TimeDelta::FromSeconds(300);
+ initial_policy_.mutable_ac_delays()->set_screen_dim_ms(0);
+ initial_policy_.mutable_ac_delays()->set_screen_off_ms(0);
+ initial_policy_.mutable_ac_delays()->set_screen_lock_ms(0);
+ initial_policy_.mutable_ac_delays()->set_idle_ms(kIdleDelay.InMilliseconds());
+ initial_policy_.set_ac_idle_action(PowerManagementPolicy_Action_SHUT_DOWN);
+ initial_policy_.set_lid_closed_action(PowerManagementPolicy_Action_SHUT_DOWN);
+
+ const int kThreshold = 10;
+ prefs_.SetInt64(kTpmCounterSuspendThresholdPref, kThreshold);
+ Init();
+
+ // With the count below the threshold, the "shut down" lid-closed action
+ // should be honored.
+ controller_.HandleTpmStatus(kThreshold - 1);
+ delegate_.set_lid_state(LID_CLOSED);
+ controller_.HandleLidStateChange(LID_CLOSED);
+ EXPECT_EQ(kShutDown, delegate_.GetActions());
+ delegate_.set_lid_state(LID_OPEN);
+ controller_.HandleLidStateChange(LID_OPEN);
+
+ // Ditto for the idle action.
+ EXPECT_TRUE(AdvanceTimeAndTriggerTimeout(kIdleDelay));
+ EXPECT_EQ(kShutDown, delegate_.GetActions());
+ controller_.HandleUserActivity();
+
+ // If the count reaches the threshold, the system should suspend instead of
+ // shutting down.
+ controller_.HandleTpmStatus(kThreshold);
+ delegate_.set_lid_state(LID_CLOSED);
+ controller_.HandleLidStateChange(LID_CLOSED);
+ EXPECT_EQ(kSuspend, delegate_.GetActions());
+ delegate_.set_lid_state(LID_OPEN);
+ controller_.HandleLidStateChange(LID_OPEN);
+
+ EXPECT_TRUE(AdvanceTimeAndTriggerTimeout(kIdleDelay));
+ EXPECT_EQ(kSuspend, delegate_.GetActions());
+ controller_.HandleUserActivity();
+
+ // If non-"shut down" actions are set, they shouldn't be overridden.
+ initial_policy_.set_ac_idle_action(PowerManagementPolicy_Action_DO_NOTHING);
+ initial_policy_.set_lid_closed_action(
+ PowerManagementPolicy_Action_STOP_SESSION);
+ controller_.HandlePolicyChange(initial_policy_);
+
+ delegate_.set_lid_state(LID_CLOSED);
+ controller_.HandleLidStateChange(LID_CLOSED);
+ EXPECT_EQ(kStopSession, delegate_.GetActions());
+ delegate_.set_lid_state(LID_OPEN);
+ controller_.HandleLidStateChange(LID_OPEN);
+
+ EXPECT_TRUE(AdvanceTimeAndTriggerTimeout(kIdleDelay));
+ EXPECT_EQ(kNoActions, delegate_.GetActions());
+ controller_.HandleUserActivity();
+}
+
} // namespace policy
} // namespace power_manager