power: Rewrite Suspender to use a state machine.

Simplify the Suspender class, making it perform a single
"prepare" step after receiving a suspend request and an
"undo prepare" step when the request is complete.
Previously, it did the prepare/unprepare dance for each
suspend _attempt_ (of which there can be multiple for a
given request in the event of dark resume or a failed
attempt).

Also run the event loop before resuspending from dark resume
in anticipation of later features where the system will
perform additional work while resumed. Suspender previously
had no dark-resume-related tests, so add a bunch of those.

BUG=chromium:384488
TEST=added more tests; also manually tested that dark resume
     works as before on link

Change-Id: I21ff21d18bbcd59a8effeca0e403c227d8045cb6
Reviewed-on: https://chromium-review.googlesource.com/205357
Reviewed-by: Daniel Erat <[email protected]>
Tested-by: Daniel Erat <[email protected]>
Commit-Queue: Daniel Erat <[email protected]>
diff --git a/powerd/daemon.cc b/powerd/daemon.cc
index 761504c..12920d9 100644
--- a/powerd/daemon.cc
+++ b/powerd/daemon.cc
@@ -28,7 +28,6 @@
 #include "power_manager/powerd/policy/internal_backlight_controller.h"
 #include "power_manager/powerd/policy/keyboard_backlight_controller.h"
 #include "power_manager/powerd/policy/state_controller.h"
-#include "power_manager/powerd/policy/suspender.h"
 #include "power_manager/powerd/system/ambient_light_sensor.h"
 #include "power_manager/powerd/system/audio_client.h"
 #include "power_manager/powerd/system/dark_resume.h"
@@ -329,137 +328,6 @@
   DISALLOW_COPY_AND_ASSIGN(StateControllerDelegate);
 };
 
-// Performs actions on behalf of Suspender.
-class Daemon::SuspenderDelegate : public policy::Suspender::Delegate {
- public:
-  SuspenderDelegate(Daemon* daemon) : daemon_(daemon) {}
-  virtual ~SuspenderDelegate() {}
-
-  // Delegate implementation:
-  virtual int GetInitialId() OVERRIDE {
-    // Take powerd's PID modulo 2**15 (/proc/sys/kernel/pid_max is currently
-    // 2**15, but just in case...) and multiply it by 2**16, leaving it able to
-    // fit in a signed 32-bit int. This allows for 2**16 suspend attempts and
-    // suspend delays per powerd run before wrapping or intruding on another
-    // run's ID range (neither of which should be particularly problematic, but
-    // doing this reduces the chances of a confused client that's using stale
-    // IDs from a previous powerd run being able to conflict with the new run's
-    // IDs).
-    return (getpid() % 32768) * 65536 + 1;
-  }
-
-  virtual bool IsLidClosed() OVERRIDE {
-    return daemon_->input_->QueryLidState() == LID_CLOSED;
-  }
-
-  virtual bool GetWakeupCount(uint64* wakeup_count) OVERRIDE {
-    DCHECK(wakeup_count);
-    base::FilePath path(kWakeupCountPath);
-    std::string buf;
-    if (base::ReadFileToString(path, &buf)) {
-      base::TrimWhitespaceASCII(buf, base::TRIM_TRAILING, &buf);
-      if (base::StringToUint64(buf, wakeup_count))
-        return true;
-
-      LOG(ERROR) << "Could not parse wakeup count from \"" << buf << "\"";
-    } else {
-      LOG(ERROR) << "Could not read " << kWakeupCountPath;
-    }
-    return false;
-  }
-
-  virtual void SetSuspendAnnounced(bool announced) OVERRIDE {
-    if (announced) {
-      if (base::WriteFile(daemon_->suspend_announced_path_, NULL, 0) < 0) {
-        PLOG(ERROR) << "Couldn't create "
-                    << daemon_->suspend_announced_path_.value();
-      }
-    } else {
-      if (!base::DeleteFile(daemon_->suspend_announced_path_, false)) {
-        PLOG(ERROR) << "Couldn't delete "
-                    << daemon_->suspend_announced_path_.value();
-      }
-    }
-  }
-
-  virtual bool GetSuspendAnnounced() OVERRIDE {
-    return base::PathExists(daemon_->suspend_announced_path_);
-  }
-
-  virtual void PrepareForSuspendAnnouncement() OVERRIDE {
-    // When going to suspend, notify the backlight controller so it can turn
-    // the backlight off and tell the kernel to resume the current level
-    // after resuming.  This must occur before Chrome is told that the system
-    // is going to suspend (Chrome turns the display back on while leaving
-    // the backlight off).
-    daemon_->SetBacklightsSuspended(true);
-  }
-
-  virtual void HandleCanceledSuspendAnnouncement() OVERRIDE {
-    // Undo the earlier call.
-    daemon_->SetBacklightsSuspended(false);
-  }
-
-  virtual void PrepareForSuspend() {
-    daemon_->PrepareForSuspend();
-  }
-
-  virtual SuspendResult Suspend(uint64 wakeup_count,
-                                bool wakeup_count_valid,
-                                base::TimeDelta duration) OVERRIDE {
-    std::string args;
-    if (wakeup_count_valid) {
-      args += base::StringPrintf(" --suspend_wakeup_count_valid"
-                                 " --suspend_wakeup_count %" PRIu64,
-                                 wakeup_count);
-    }
-    if (duration != base::TimeDelta()) {
-      args += base::StringPrintf(" --suspend_duration %" PRId64,
-                                 duration.InSeconds());
-    }
-
-    // These exit codes are defined in scripts/powerd_suspend.
-    switch (RunSetuidHelper("suspend", args, true)) {
-      case 0:
-        return SUSPEND_SUCCESSFUL;
-      case 1:
-        return SUSPEND_FAILED;
-      case 2:  // Canceled before write to wakeup_count.
-        return SUSPEND_CANCELED;
-      case 3:  // Canceled after write to wakeup_count.
-        return SUSPEND_CANCELED;
-      default:
-        LOG(ERROR) << "Treating unexpected exit code as suspend failure";
-        return SUSPEND_FAILED;
-    }
-  }
-
-  virtual void HandleSuspendAttemptCompletion(
-      bool suspend_was_successful,
-      int num_suspend_attempts) OVERRIDE {
-    daemon_->HandleSuspendAttemptCompletion(suspend_was_successful,
-                                            num_suspend_attempts);
-  }
-
-  virtual void HandleCanceledSuspendRequest(int num_suspend_attempts) {
-    daemon_->metrics_collector_->HandleCanceledSuspendRequest(
-        num_suspend_attempts);
-  }
-
-  virtual void ShutDownForFailedSuspend() OVERRIDE {
-    daemon_->ShutDown(SHUTDOWN_MODE_POWER_OFF, SHUTDOWN_REASON_SUSPEND_FAILED);
-  }
-
-  virtual void ShutDownForDarkResume() OVERRIDE {
-    daemon_->ShutDown(SHUTDOWN_MODE_POWER_OFF, SHUTDOWN_REASON_DARK_RESUME);
-  }
-
- private:
-  Daemon* daemon_;  // weak
-
-  DISALLOW_COPY_AND_ASSIGN(SuspenderDelegate);
-};
-
 Daemon::Daemon(const base::FilePath& read_write_prefs_dir,
                const base::FilePath& read_only_prefs_dir,
                const base::FilePath& run_dir)
@@ -481,7 +349,6 @@
       peripheral_battery_watcher_(new system::PeripheralBatteryWatcher),
       power_supply_(new system::PowerSupply),
       dark_resume_(new system::DarkResume),
-      suspender_delegate_(new SuspenderDelegate(this)),
       suspender_(new policy::Suspender),
       metrics_collector_(new MetricsCollector),
       shutting_down_(false),
@@ -578,8 +445,7 @@
   OnPowerStatusUpdate();
 
   dark_resume_->Init(power_supply_.get(), prefs_.get());
-  suspender_->Init(suspender_delegate_.get(), dbus_sender_.get(),
-                   dark_resume_.get(), prefs_.get());
+  suspender_->Init(this, dbus_sender_.get(), dark_resume_.get(), prefs_.get());
 
   CHECK(input_->Init(prefs_.get(), udev_.get()));
   input_controller_->Init(input_.get(), this, display_watcher_.get(),
@@ -641,65 +507,6 @@
   }
 }
 
-void Daemon::PrepareForSuspend() {
-  // This command is run synchronously to ensure that it finishes before the
-  // system is suspended.
-  // TODO(derat): Remove this once it's logged by the kernel:
-  // http://crosbug.com/p/16132
-  if (log_suspend_with_mosys_eventlog_)
-    RunSetuidHelper("mosys_eventlog", "--mosys_eventlog_code=0xa7", true);
-
-  // Do not let suspend change the console terminal.
-  if (lock_vt_before_suspend_)
-    RunSetuidHelper("lock_vt", "", true);
-
-  // Touch a file that crash-reporter can inspect later to determine
-  // whether the system was suspended while an unclean shutdown occurred.
-  // If the file already exists, assume that crash-reporter hasn't seen it
-  // yet and avoid unlinking it after resume.
-  created_suspended_state_file_ = false;
-  const base::FilePath kStatePath(kSuspendedStatePath);
-  if (!base::PathExists(kStatePath)) {
-    if (base::WriteFile(kStatePath, NULL, 0) == 0)
-      created_suspended_state_file_ = true;
-    else
-      LOG(WARNING) << "Unable to create " << kSuspendedStatePath;
-  }
-
-  power_supply_->SetSuspended(true);
-  audio_client_->MuteSystem();
-  metrics_collector_->PrepareForSuspend();
-}
-
-void Daemon::HandleSuspendAttemptCompletion(bool suspend_was_successful,
-                                            int num_suspend_attempts) {
-  SetBacklightsSuspended(false);
-  audio_client_->RestoreMutedState();
-  power_supply_->SetSuspended(false);
-
-  // Allow virtual terminal switching again.
-  if (lock_vt_before_suspend_)
-    RunSetuidHelper("unlock_vt", "", true);
-
-  if (created_suspended_state_file_) {
-    if (!base::DeleteFile(base::FilePath(kSuspendedStatePath), false))
-      LOG(ERROR) << "Failed to delete " << kSuspendedStatePath;
-  }
-
-  if (suspend_was_successful)
-    metrics_collector_->HandleResume(num_suspend_attempts);
-
-  // TODO(derat): Remove this once it's logged by the kernel:
-  // http://crosbug.com/p/16132
-  if (log_suspend_with_mosys_eventlog_)
-    RunSetuidHelper("mosys_eventlog", "--mosys_eventlog_code=0xa8", false);
-
-  // StateController may synchronously trigger another suspend attempt if the
-  // lid is still closed. Notify it last to ensure that all other cleanup is
-  // already done.
-  state_controller_->HandleResume();
-}
-
 void Daemon::HandleLidClosed() {
   if (state_controller_initialized_)
     state_controller_->HandleLidStateChange(LID_CLOSED);
@@ -738,6 +545,156 @@
   metrics_collector_->SendPowerButtonAcknowledgmentDelayMetric(delay);
 }
 
+int Daemon::GetInitialSuspendId() {
+  // Take powerd's PID modulo 2**15 (/proc/sys/kernel/pid_max is currently
+  // 2**15, but just in case...) and multiply it by 2**16, leaving it able to
+  // fit in a signed 32-bit int. This allows for 2**16 suspend attempts and
+  // suspend delays per powerd run before wrapping or intruding on another
+  // run's ID range (neither of which should be particularly problematic, but
+  // doing this reduces the chances of a confused client that's using stale
+  // IDs from a previous powerd run being able to conflict with the new run's
+  // IDs).
+  return (getpid() % 32768) * 65536 + 1;
+}
+
+bool Daemon::IsLidClosedForSuspend() {
+  return input_->QueryLidState() == LID_CLOSED;
+}
+
+bool Daemon::ReadSuspendWakeupCount(uint64* wakeup_count) {
+  DCHECK(wakeup_count);
+  base::FilePath path(kWakeupCountPath);
+  std::string buf;
+  if (base::ReadFileToString(path, &buf)) {
+    base::TrimWhitespaceASCII(buf, base::TRIM_TRAILING, &buf);
+    if (base::StringToUint64(buf, wakeup_count))
+      return true;
+
+    LOG(ERROR) << "Could not parse wakeup count from \"" << buf << "\"";
+  } else {
+    LOG(ERROR) << "Could not read " << kWakeupCountPath;
+  }
+  return false;
+}
+
+void Daemon::SetSuspendAnnounced(bool announced) {
+  if (announced) {
+    if (base::WriteFile(suspend_announced_path_, NULL, 0) < 0)
+      PLOG(ERROR) << "Couldn't create " << suspend_announced_path_.value();
+  } else {
+    if (!base::DeleteFile(suspend_announced_path_, false))
+      PLOG(ERROR) << "Couldn't delete " << suspend_announced_path_.value();
+  }
+}
+
+bool Daemon::GetSuspendAnnounced() {
+  return base::PathExists(suspend_announced_path_);
+}
+
+void Daemon::PrepareToSuspend() {
+  // Before announcing the suspend request, notify the backlight controller so
+  // it can turn the backlight off and tell the kernel to resume the current
+  // level after resuming.  This must occur before Chrome is told that the
+  // system is going to suspend (Chrome turns the display back on while leaving
+  // the backlight off).
+  SetBacklightsSuspended(true);
+
+  // Do not let suspend change the console terminal.
+  if (lock_vt_before_suspend_)
+    RunSetuidHelper("lock_vt", "", true);
+
+  power_supply_->SetSuspended(true);
+  audio_client_->MuteSystem();
+  metrics_collector_->PrepareForSuspend();
+}
+
+policy::Suspender::Delegate::SuspendResult Daemon::DoSuspend(
+    uint64 wakeup_count,
+    bool wakeup_count_valid,
+    base::TimeDelta duration) {
+  // Touch a file that crash-reporter can inspect later to determine
+  // whether the system was suspended while an unclean shutdown occurred.
+  // If the file already exists, assume that crash-reporter hasn't seen it
+  // yet and avoid unlinking it after resume.
+  created_suspended_state_file_ = false;
+  const base::FilePath kStatePath(kSuspendedStatePath);
+  if (!base::PathExists(kStatePath)) {
+    if (base::WriteFile(kStatePath, NULL, 0) == 0)
+      created_suspended_state_file_ = true;
+    else
+      PLOG(ERROR) << "Unable to create " << kSuspendedStatePath;
+  }
+
+  // This command is run synchronously to ensure that it finishes before the
+  // system is suspended.
+  // TODO(derat): Remove this once it's logged by the kernel:
+  // http://crosbug.com/p/16132
+  if (log_suspend_with_mosys_eventlog_)
+    RunSetuidHelper("mosys_eventlog", "--mosys_eventlog_code=0xa7", true);
+
+  std::string args;
+  if (wakeup_count_valid) {
+    args += base::StringPrintf(" --suspend_wakeup_count_valid"
+                               " --suspend_wakeup_count %" PRIu64,
+                               wakeup_count);
+  }
+  if (duration != base::TimeDelta()) {
+    args += base::StringPrintf(" --suspend_duration %" PRId64,
+                               duration.InSeconds());
+  }
+  const int exit_code = RunSetuidHelper("suspend", args, true);
+
+  // TODO(derat): Remove this once it's logged by the kernel:
+  // http://crosbug.com/p/16132
+  if (log_suspend_with_mosys_eventlog_)
+    RunSetuidHelper("mosys_eventlog", "--mosys_eventlog_code=0xa8", false);
+
+  if (created_suspended_state_file_) {
+    if (!base::DeleteFile(base::FilePath(kSuspendedStatePath), false))
+      PLOG(ERROR) << "Failed to delete " << kSuspendedStatePath;
+  }
+
+  // These exit codes are defined in powerd/powerd_suspend.
+  switch (exit_code) {
+    case 0:
+      return SUSPEND_SUCCESSFUL;
+    case 1:
+      return SUSPEND_FAILED;
+    case 2:  // Wakeup event received before write to wakeup_count.
+    case 3:  // Wakeup event received after write to wakeup_count.
+      return SUSPEND_CANCELED;
+    default:
+      LOG(ERROR) << "Treating unexpected exit code " << exit_code
+                 << " as suspend failure";
+      return SUSPEND_FAILED;
+  }
+}
+
+void Daemon::UndoPrepareToSuspend(bool success, int num_suspend_attempts) {
+  audio_client_->RestoreMutedState();
+  power_supply_->SetSuspended(false);
+
+  // Allow virtual terminal switching again.
+  if (lock_vt_before_suspend_)
+    RunSetuidHelper("unlock_vt", "", true);
+
+  SetBacklightsSuspended(false);
+  state_controller_->HandleResume();
+
+  if (success)
+    metrics_collector_->HandleResume(num_suspend_attempts);
+  else if (num_suspend_attempts > 0)
+    metrics_collector_->HandleCanceledSuspendRequest(num_suspend_attempts);
+}
+
+void Daemon::ShutDownForFailedSuspend() {
+  ShutDown(SHUTDOWN_MODE_POWER_OFF, SHUTDOWN_REASON_SUSPEND_FAILED);
+}
+
+void Daemon::ShutDownForDarkResume() {
+  ShutDown(SHUTDOWN_MODE_POWER_OFF, SHUTDOWN_REASON_DARK_RESUME);
+}
+
 void Daemon::OnAudioStateChange(bool active) {
   // |state_controller_| needs to be ready at this point -- since notifications
   // only arrive when the audio state changes, skipping any is unsafe.
diff --git a/powerd/daemon.h b/powerd/daemon.h
index 1f6654c..395d299 100644
--- a/powerd/daemon.h
+++ b/powerd/daemon.h
@@ -20,6 +20,7 @@
 #include "power_manager/common/prefs_observer.h"
 #include "power_manager/powerd/policy/backlight_controller_observer.h"
 #include "power_manager/powerd/policy/input_controller.h"
+#include "power_manager/powerd/policy/suspender.h"
 #include "power_manager/powerd/system/audio_observer.h"
 #include "power_manager/powerd/system/power_supply_observer.h"
 
@@ -62,6 +63,7 @@
 // Main class within the powerd daemon that ties all other classes together.
 class Daemon : public policy::BacklightControllerObserver,
                public policy::InputController::Delegate,
+               public policy::Suspender::Delegate,
                public system::AudioObserver,
                public system::PowerSupplyObserver {
  public:
@@ -78,14 +80,6 @@
       policy::BacklightController::BrightnessChangeCause cause,
       policy::BacklightController* source) OVERRIDE;
 
-  // Called by |suspender_| just before a suspend attempt begins.
-  void PrepareForSuspend();
-
-  // Called by |suspender_| after the completion of a suspend/resume cycle
-  // (which did not necessarily succeed).
-  void HandleSuspendAttemptCompletion(bool suspend_was_successful,
-                                      int num_suspend_attempts);
-
   // Overridden from policy::InputController::Delegate:
   virtual void HandleLidClosed() OVERRIDE;
   virtual void HandleLidOpened() OVERRIDE;
@@ -96,6 +90,21 @@
   virtual void ReportPowerButtonAcknowledgmentDelay(base::TimeDelta delay)
       OVERRIDE;
 
+  // Overridden from policy::Suspender::Delegate:
+  virtual int GetInitialSuspendId() OVERRIDE;
+  virtual bool IsLidClosedForSuspend() OVERRIDE;
+  virtual bool ReadSuspendWakeupCount(uint64* wakeup_count) OVERRIDE;
+  virtual void SetSuspendAnnounced(bool announced) OVERRIDE;
+  virtual bool GetSuspendAnnounced() OVERRIDE;
+  virtual void PrepareToSuspend() OVERRIDE;
+  virtual SuspendResult DoSuspend(uint64 wakeup_count,
+                                  bool wakeup_count_valid,
+                                  base::TimeDelta duration) OVERRIDE;
+  virtual void UndoPrepareToSuspend(bool success,
+                                    int num_suspend_attempts) OVERRIDE;
+  virtual void ShutDownForFailedSuspend() OVERRIDE;
+  virtual void ShutDownForDarkResume() OVERRIDE;
+
   // Overridden from system::AudioObserver:
   virtual void OnAudioStateChange(bool active) OVERRIDE;
 
@@ -237,7 +246,6 @@
   scoped_ptr<system::PeripheralBatteryWatcher> peripheral_battery_watcher_;
   scoped_ptr<system::PowerSupply> power_supply_;
   scoped_ptr<system::DarkResume> dark_resume_;
-  scoped_ptr<SuspenderDelegate> suspender_delegate_;
   scoped_ptr<policy::Suspender> suspender_;
 
   scoped_ptr<MetricsCollector> metrics_collector_;
diff --git a/powerd/metrics_collector.h b/powerd/metrics_collector.h
index 99a96da..8295b5b 100644
--- a/powerd/metrics_collector.h
+++ b/powerd/metrics_collector.h
@@ -59,17 +59,17 @@
   void HandlePowerStatusUpdate(const system::PowerStatus& status);
   void HandleShutdown(ShutdownReason reason);
 
-  // Called just before a suspend attempt is performed.
+  // Called at the beginning of a suspend request (which may consist of multiple
+  // suspend attempts).
   void PrepareForSuspend();
 
-  // Called after the system has successfully suspended and resumed.
-  // |num_suspend_attempts| contains the number of attempts up to and including
-  // the one in which the system successfully suspended.
+  // Called at the end of a successful suspend request. |num_suspend_attempts|
+  // contains the number of attempts up to and including the one in which the
+  // system successfully suspended.
   void HandleResume(int num_suspend_attempts);
 
-  // Called after a request to suspend the system (that is, a series of one or
-  // more suspend attempts performed in response to e.g. the lid being closed)
-  // is canceled.
+  // Called after a suspend request (that is, a series of one or more suspend
+  // attempts performed in response to e.g. the lid being closed) is canceled.
   void HandleCanceledSuspendRequest(int num_suspend_attempts);
 
   // Generates UMA metrics on when leaving the idle state.
diff --git a/powerd/policy/suspend_delay_controller.cc b/powerd/policy/suspend_delay_controller.cc
index 7e6b087..7271fd9 100644
--- a/powerd/policy/suspend_delay_controller.cc
+++ b/powerd/policy/suspend_delay_controller.cc
@@ -10,7 +10,6 @@
 #include <base/strings/string_number_conversions.h>
 #include <chromeos/dbus/service_constants.h>
 
-#include "power_manager/common/dbus_sender.h"
 #include "power_manager/common/util.h"
 #include "power_manager/powerd/policy/suspend_delay_observer.h"
 #include "power_manager/proto_bindings/suspend.pb.h"
@@ -26,16 +25,12 @@
 
 }  // namespace
 
-SuspendDelayController::SuspendDelayController(DBusSenderInterface* dbus_sender,
-                                               int initial_delay_id)
-    : dbus_sender_(dbus_sender),
-      next_delay_id_(initial_delay_id),
+SuspendDelayController::SuspendDelayController(int initial_delay_id)
+    : next_delay_id_(initial_delay_id),
       current_suspend_id_(0) {
-  DCHECK(dbus_sender_);
 }
 
 SuspendDelayController::~SuspendDelayController() {
-  dbus_sender_ = NULL;
 }
 
 void SuspendDelayController::AddObserver(SuspendDelayObserver* observer) {
@@ -86,11 +81,11 @@
   int suspend_id = info.suspend_id();
   LOG(INFO) << "Got notification that delay " << delay_id
             << " (" << GetDelayDescription(delay_id) << ") is ready for "
-            << "suspend attempt " << suspend_id << " from " << dbus_client;
+            << "suspend request " << suspend_id << " from " << dbus_client;
 
   if (suspend_id != current_suspend_id_) {
-    LOG(WARNING) << "Ignoring readiness notification for wrong suspend attempt "
-                 << "(got " << suspend_id << ", currently attempting "
+    LOG(WARNING) << "Ignoring readiness notification for wrong suspend request "
+                 << "(got " << suspend_id << ", currently on "
                  << current_suspend_id_ << ")";
     return;
   }
@@ -130,10 +125,10 @@
        it != registered_delays_.end(); ++it)
     delay_ids_being_waited_on_.insert(it->first);
 
-  LOG(INFO) << "Announcing suspend attempt " << current_suspend_id_
+  LOG(INFO) << "Announcing suspend request " << current_suspend_id_
             << " with " << delay_ids_being_waited_on_.size()
             << " pending delay(s) and " << old_count
-            << " outstanding delay(s) from previous attempt";
+            << " outstanding delay(s) from previous request";
   if (delay_ids_being_waited_on_.empty()) {
     PostNotifyObserversTask(current_suspend_id_);
   } else {
@@ -147,10 +142,6 @@
     delay_expiration_timer_.Start(FROM_HERE, max_timeout, this,
         &SuspendDelayController::OnDelayExpiration);
   }
-
-  SuspendImminent proto;
-  proto.set_suspend_id(current_suspend_id_);
-  dbus_sender_->EmitSignalWithProtocolBuffer(kSuspendImminentSignal, proto);
 }
 
 std::string SuspendDelayController::GetDelayDescription(int delay_id) const {
diff --git a/powerd/policy/suspend_delay_controller.h b/powerd/policy/suspend_delay_controller.h
index 8c8f00a..46b0173 100644
--- a/powerd/policy/suspend_delay_controller.h
+++ b/powerd/policy/suspend_delay_controller.h
@@ -30,8 +30,7 @@
 // time to do last-minute cleanup.
 class SuspendDelayController {
  public:
-  SuspendDelayController(DBusSenderInterface* dbus_sender,
-                         int initial_delay_id);
+  explicit SuspendDelayController(int initial_delay_id);
   ~SuspendDelayController();
 
   bool ready_for_suspend() const { return delay_ids_being_waited_on_.empty(); }
@@ -101,9 +100,6 @@
   // Invokes OnReadyForSuspend() on |observers_|.
   void NotifyObservers(int suspend_id);
 
-  // Used to emit D-Bus signals.
-  DBusSenderInterface* dbus_sender_;
-
   // Map from delay ID to registered delay.
   typedef std::map<int, DelayInfo> DelayInfoMap;
   DelayInfoMap registered_delays_;
diff --git a/powerd/policy/suspend_delay_controller_unittest.cc b/powerd/policy/suspend_delay_controller_unittest.cc
index 57a3644..e60737b 100644
--- a/powerd/policy/suspend_delay_controller_unittest.cc
+++ b/powerd/policy/suspend_delay_controller_unittest.cc
@@ -11,7 +11,6 @@
 #include <chromeos/dbus/service_constants.h>
 #include <gtest/gtest.h>
 
-#include "power_manager/common/dbus_sender_stub.h"
 #include "power_manager/common/test_main_loop_runner.h"
 #include "power_manager/powerd/policy/suspend_delay_observer.h"
 #include "power_manager/proto_bindings/suspend.pb.h"
@@ -48,8 +47,7 @@
 
 class SuspendDelayControllerTest : public ::testing::Test {
  public:
-  SuspendDelayControllerTest()
-      : controller_(&dbus_sender_, 1 /* initial_delay_id */) {
+  SuspendDelayControllerTest() : controller_(1) {
     controller_.AddObserver(&observer_);
   }
 
@@ -86,19 +84,7 @@
     controller_.HandleSuspendReadiness(info, client);
   }
 
-  // Tests that exactly one SuspendImminent signal has been emitted via
-  // |dbus_sender_| and returns the suspend ID from it.  Clears sent signals
-  // before returning.
-  int GetSuspendId() {
-    EXPECT_EQ(1, dbus_sender_.num_sent_signals());
-    SuspendImminent proto;
-    EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendImminentSignal, &proto));
-    dbus_sender_.ClearSentSignals();
-    return proto.suspend_id();
-  }
-
   TestObserver observer_;
-  DBusSenderStub dbus_sender_;
   SuspendDelayController controller_;
 
   DISALLOW_COPY_AND_ASSIGN(SuspendDelayControllerTest);
@@ -115,7 +101,6 @@
   // suspending -- there are no delays to wait for.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_TRUE(controller_.ready_for_suspend());
 
   // The observer should be notified that it's safe to suspend.
@@ -133,7 +118,6 @@
   // The controller shouldn't report readiness now; it's waiting on the delay.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
 
   // Tell the controller that the delay is ready and check that the controller
@@ -155,7 +139,6 @@
   // The controller should immediately report readiness.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_TRUE(controller_.ready_for_suspend());
   EXPECT_TRUE(observer_.RunUntilReadyForSuspend());
   EXPECT_TRUE(controller_.ready_for_suspend());
@@ -170,7 +153,6 @@
   // Request suspending.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
 
   // If the delay is unregistered while the controller is waiting for it, the
@@ -185,7 +167,6 @@
   // Request suspending before any delays have been registered.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_TRUE(controller_.ready_for_suspend());
 
   // Register a delay now.  The controller should still report readiness.
@@ -198,7 +179,6 @@
   // Request suspending again.  The controller should say it isn't ready now.
   const int kNextSuspendId = 6;
   controller_.PrepareForSuspend(kNextSuspendId);
-  EXPECT_EQ(kNextSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
 
   HandleSuspendReadiness(delay_id, kNextSuspendId, kClient);
@@ -216,7 +196,6 @@
   // The controller should report readiness due to the timeout being hit.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
   EXPECT_TRUE(observer_.RunUntilReadyForSuspend());
   EXPECT_TRUE(controller_.ready_for_suspend());
@@ -234,7 +213,6 @@
   // The delay should have been removed.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_TRUE(controller_.ready_for_suspend());
   EXPECT_TRUE(observer_.RunUntilReadyForSuspend());
   EXPECT_TRUE(controller_.ready_for_suspend());
@@ -247,7 +225,6 @@
 
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
 
   // If the client is disconnected while the controller is waiting, it should
@@ -267,13 +244,11 @@
   // Request suspending.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
 
   // Before confirming that the delay is ready, request suspending again.
   const int kNextSuspendId = 6;
   controller_.PrepareForSuspend(kNextSuspendId);
-  EXPECT_EQ(kNextSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
 
   // Report readiness, but do it on behalf of the original suspend attempt.  The
@@ -304,7 +279,6 @@
   // until both delays have confirmed their readiness.
   const int kSuspendId = 5;
   controller_.PrepareForSuspend(kSuspendId);
-  EXPECT_EQ(kSuspendId, GetSuspendId());
   EXPECT_FALSE(controller_.ready_for_suspend());
   HandleSuspendReadiness(delay_id2, kSuspendId, kClient2);
   EXPECT_FALSE(controller_.ready_for_suspend());
diff --git a/powerd/policy/suspender.cc b/powerd/policy/suspender.cc
index 32794a7..86826f5 100644
--- a/powerd/policy/suspender.cc
+++ b/powerd/policy/suspender.cc
@@ -29,12 +29,12 @@
   suspender_->clock_->set_current_wall_time_for_testing(wall_time);
 }
 
-bool Suspender::TestApi::TriggerRetryTimeout() {
-  if (!suspender_->retry_suspend_timer_.IsRunning())
+bool Suspender::TestApi::TriggerResuspendTimeout() {
+  if (!suspender_->resuspend_timer_.IsRunning())
     return false;
 
-  suspender_->retry_suspend_timer_.Stop();
-  suspender_->RetrySuspend();
+  suspender_->resuspend_timer_.Stop();
+  suspender_->HandleEvent(EVENT_READY_TO_RESUSPEND);
   return true;
 }
 
@@ -43,31 +43,33 @@
       dbus_sender_(NULL),
       dark_resume_(NULL),
       clock_(new Clock),
-      waiting_for_readiness_(false),
-      suspend_id_(0),
+      state_(STATE_IDLE),
+      handling_event_(false),
+      processing_queued_events_(false),
+      suspend_request_id_(0),
+      suspend_request_supplied_wakeup_count_(false),
+      suspend_request_wakeup_count_(0),
       wakeup_count_(0),
       wakeup_count_valid_(false),
-      got_external_wakeup_count_(false),
       max_retries_(0),
-      num_attempts_(0),
-      shutting_down_(false) {
+      current_num_attempts_(0),
+      initial_num_attempts_(0) {
 }
 
 Suspender::~Suspender() {
 }
 
-void Suspender::Init(Delegate *delegate,
-                     DBusSenderInterface *dbus_sender,
+void Suspender::Init(Delegate* delegate,
+                     DBusSenderInterface* dbus_sender,
                      system::DarkResumeInterface *dark_resume,
-                     PrefsInterface *prefs) {
+                     PrefsInterface* prefs) {
   delegate_ = delegate;
   dbus_sender_ = dbus_sender;
   dark_resume_ = dark_resume;
 
-  const int initial_id = delegate_->GetInitialId();
-  suspend_id_ = initial_id - 1;
-  suspend_delay_controller_.reset(
-      new SuspendDelayController(dbus_sender_, initial_id));
+  const int initial_id = delegate_->GetInitialSuspendId();
+  suspend_request_id_ = initial_id - 1;
+  suspend_delay_controller_.reset(new SuspendDelayController(initial_id));
   suspend_delay_controller_->AddObserver(this);
 
   int64 retry_delay_ms = 0;
@@ -80,16 +82,21 @@
   // but before emitting SuspendDone.
   if (delegate_->GetSuspendAnnounced()) {
     LOG(INFO) << "Previous run exited mid-suspend; emitting SuspendDone";
-    AnnounceSuspendCompletion(0, base::TimeDelta());
+    EmitSuspendDoneSignal(0, base::TimeDelta());
+    delegate_->SetSuspendAnnounced(false);
   }
 }
 
 void Suspender::RequestSuspend() {
-  StartSuspendAttempt(0, false);
+  suspend_request_supplied_wakeup_count_ = false;
+  suspend_request_wakeup_count_ = 0;
+  HandleEvent(EVENT_SUSPEND_REQUESTED);
 }
 
 void Suspender::RequestSuspendWithExternalWakeupCount(uint64 wakeup_count) {
-  StartSuspendAttempt(wakeup_count, true);
+  suspend_request_supplied_wakeup_count_ = true;
+  suspend_request_wakeup_count_ = wakeup_count;
+  HandleEvent(EVENT_SUSPEND_REQUESTED);
 }
 
 void Suspender::RegisterSuspendDelay(
@@ -153,19 +160,18 @@
 }
 
 void Suspender::HandleLidOpened() {
-  CancelSuspend();
+  HandleEvent(EVENT_USER_ACTIVITY);
 }
 
 void Suspender::HandleUserActivity() {
   // Avoid canceling suspend for errant touchpad, power button, etc. events
   // that can be generated by closing the lid.
-  if (!delegate_->IsLidClosed())
-    CancelSuspend();
+  if (!delegate_->IsLidClosedForSuspend())
+    HandleEvent(EVENT_USER_ACTIVITY);
 }
 
 void Suspender::HandleShutdown() {
-  shutting_down_ = true;
-  CancelSuspend();
+  HandleEvent(EVENT_SHUTDOWN_STARTED);
 }
 
 void Suspender::HandleDBusNameOwnerChanged(const std::string& name,
@@ -176,181 +182,204 @@
 }
 
 void Suspender::OnReadyForSuspend(int suspend_id) {
-  if (waiting_for_readiness_ && suspend_id == suspend_id_) {
-    LOG(INFO) << "Ready to suspend";
-    waiting_for_readiness_ = false;
-    Suspend();
-  }
+  if (suspend_id == suspend_request_id_)
+    HandleEvent(EVENT_SUSPEND_DELAYS_READY);
 }
 
-void Suspender::StartSuspendAttempt(uint64 external_wakeup_count,
-                                    bool use_external_wakeup_count) {
-  // Suspend shouldn't be requested after the system has started shutting
-  // down, but if it is, avoid doing anything.
-  if (shutting_down_) {
-    LOG(ERROR) << "Not starting suspend attempt; shutdown in progress";
+void Suspender::HandleEvent(Event event) {
+  // If a new event is received while handling an event, save it for later. This
+  // can happen when e.g. |delegate_|'s UndoPrepareToSuspend() method attempts
+  // to resuspend or ShutDownFor*() calls HandleShutdown().
+  if (handling_event_) {
+    queued_events_.push(event);
     return;
   }
 
-  // Ignore the request if a suspend attempt has already been started or if a
-  // retry is already scheduled.
-  if (waiting_for_readiness_ || retry_suspend_timer_.IsRunning())
+  handling_event_ = true;
+
+  switch (state_) {
+    case STATE_IDLE:
+      switch (event) {
+        case EVENT_SUSPEND_REQUESTED:
+          StartRequest();
+          state_ = STATE_WAITING_FOR_SUSPEND_DELAYS;
+          break;
+        case EVENT_SHUTDOWN_STARTED:
+          state_ = STATE_SHUTTING_DOWN;
+          break;
+        default:
+          break;
+      }
+      break;
+    // These two states are identical apart from the event that triggers the
+    // call to Suspend().
+    case STATE_WAITING_FOR_SUSPEND_DELAYS:
+    case STATE_WAITING_TO_RESUSPEND:
+      switch (event) {
+        case EVENT_SUSPEND_DELAYS_READY:
+          if (state_ == STATE_WAITING_FOR_SUSPEND_DELAYS)
+            state_ = Suspend();
+          break;
+        case EVENT_READY_TO_RESUSPEND:
+          if (state_ == STATE_WAITING_TO_RESUSPEND)
+            state_ = Suspend();
+          break;
+        case EVENT_USER_ACTIVITY:
+          FinishRequest(false);
+          state_ = STATE_IDLE;
+          break;
+        case EVENT_SHUTDOWN_STARTED:
+          FinishRequest(false);
+          state_ = STATE_SHUTTING_DOWN;
+          break;
+        default:
+          break;
+      }
+      break;
+    case STATE_SHUTTING_DOWN:
+      break;
+  }
+
+  handling_event_ = false;
+
+  // Let the outermost invocation of HandleEvent() deal with the queue.
+  if (processing_queued_events_)
     return;
 
-  if (use_external_wakeup_count) {
-    wakeup_count_ = external_wakeup_count;
+  // Pass queued events back into HandleEvent() one at a time.
+  processing_queued_events_ = true;
+  while (!queued_events_.empty()) {
+    Event event = queued_events_.front();
+    queued_events_.pop();
+    HandleEvent(event);
+  }
+  processing_queued_events_ = false;
+}
+
+void Suspender::StartRequest() {
+  if (suspend_request_supplied_wakeup_count_) {
+    wakeup_count_ = suspend_request_wakeup_count_;
     wakeup_count_valid_ = true;
   } else {
-    wakeup_count_valid_ = delegate_->GetWakeupCount(&wakeup_count_);
+    wakeup_count_valid_ = delegate_->ReadSuspendWakeupCount(&wakeup_count_);
   }
-  got_external_wakeup_count_ = use_external_wakeup_count;
 
-  suspend_id_++;
-  num_attempts_++;
-  waiting_for_readiness_ = true;
-  delegate_->PrepareForSuspendAnnouncement();
+  suspend_request_id_++;
+  suspend_request_start_time_ = clock_->GetCurrentWallTime();
+  current_num_attempts_ = 0;
+  initial_num_attempts_ = 0;
+
+  // Call PrepareToSuspend() before emitting SuspendImminent -- powerd needs to
+  // set the backlight level to 0 before Chrome turns the display on in response
+  // to the signal.
+  delegate_->PrepareToSuspend();
+  suspend_delay_controller_->PrepareForSuspend(suspend_request_id_);
   delegate_->SetSuspendAnnounced(true);
-  suspend_delay_controller_->PrepareForSuspend(suspend_id_);
+  EmitSuspendImminentSignal(suspend_request_id_);
 }
 
-void Suspender::Suspend() {
+void Suspender::FinishRequest(bool success) {
+  resuspend_timer_.Stop();
+  base::TimeDelta suspend_duration = std::max(base::TimeDelta(),
+      clock_->GetCurrentWallTime() - suspend_request_start_time_);
+  EmitSuspendDoneSignal(suspend_request_id_, suspend_duration);
+  delegate_->SetSuspendAnnounced(false);
+  delegate_->UndoPrepareToSuspend(success,
+      initial_num_attempts_ ? initial_num_attempts_ : current_num_attempts_);
+}
+
+Suspender::State Suspender::Suspend() {
+  system::DarkResumeInterface::Action action;
+  base::TimeDelta duration;
+  dark_resume_->PrepareForSuspendAttempt(&action, &duration);
+  switch (action) {
+    case system::DarkResumeInterface::SHUT_DOWN:
+      LOG(INFO) << "Shutting down from dark resume";
+      // Don't call FinishRequest(); we want the backlight to stay off.
+      delegate_->ShutDownForDarkResume();
+      return STATE_SHUTTING_DOWN;
+    case system::DarkResumeInterface::SUSPEND:
+      if (duration != base::TimeDelta())
+        LOG(INFO) << "Suspending for " << duration.InSeconds() << " seconds";
+      break;
+    default:
+      NOTREACHED() << "Unhandled dark resume action " << action;
+  }
+
   // Note: If this log message is changed, the power_AudioDetector test
   // must be updated.
   LOG(INFO) << "Starting suspend";
+  current_num_attempts_++;
+  const Delegate::SuspendResult result =
+      delegate_->DoSuspend(wakeup_count_, wakeup_count_valid_, duration);
 
-  bool in_dark_resume = false;
-  Delegate::SuspendResult result = Delegate::SUSPEND_SUCCESSFUL;
-  bool success = false;
-  const base::Time start_wall_time = clock_->GetCurrentWallTime();
+  // At this point, we've either resumed successfully or failed to suspend in
+  // the first place.
 
-  delegate_->PrepareForSuspend();
+  const bool in_dark_resume = dark_resume_->InDarkResume();
+  if (in_dark_resume) {
+    // To increase the chances of suspending successfully, don't check the
+    // wakeup count when in dark resume.
+    wakeup_count_ = 0;
+    wakeup_count_valid_ = false;
 
-  do {
-    system::DarkResumeInterface::Action action;
-    base::TimeDelta suspend_duration;
-    dark_resume_->PrepareForSuspendAttempt(&action, &suspend_duration);
-    switch (action) {
-      case system::DarkResumeInterface::SHUT_DOWN:
-        LOG(INFO) << "Shutting down from dark resume";
-        delegate_->ShutDownForDarkResume();
-        return;
-      case system::DarkResumeInterface::SUSPEND:
-        if (suspend_duration != base::TimeDelta()) {
-          LOG(INFO) << "Suspending for " << suspend_duration.InSeconds()
-                    << " seconds";
-        }
-        break;
-      default:
-        NOTREACHED() << "Unhandled dark resume action " << action;
+    // If the suspend attempt was successful, reschedule an immediate resuspend.
+    if (result == Delegate::SUSPEND_SUCCESSFUL) {
+      // Save the first run's number of attempts so it can be reported later.
+      if (!initial_num_attempts_)
+        initial_num_attempts_ = current_num_attempts_;
+      current_num_attempts_ = 0;
+      ScheduleResuspend(base::TimeDelta());
+      return STATE_WAITING_TO_RESUSPEND;
     }
-
-    // We don't want to use the wakeup_count in the case of a dark resume. The
-    // kernel may not have initialized some of the devices to make the dark
-    // resume as inconspicuous as possible, so allowing the user to use the
-    // system in this state would be bad.
-    result = delegate_->Suspend(wakeup_count_,
-        wakeup_count_valid_ && !in_dark_resume, suspend_duration);
-    success = result == Delegate::SUSPEND_SUCCESSFUL;
-    in_dark_resume = dark_resume_->InDarkResume();
-
-    // Failure handling for dark resume. We don't want to process events during
-    // a dark resume, even if we fail to suspend. To solve this, instead of
-    // scheduling a retry later, delay here and retry without returning from
-    // this function.
-    if (!success && in_dark_resume) {
-      if (ShutDownIfRetryLimitReached())
-        return;
-      LOG(WARNING) << "Retry #" << num_attempts_ << " from dark resume";
-      sleep(retry_delay_.InSeconds());
-      num_attempts_++;
-    }
-  } while (in_dark_resume);
+  }
 
   // Don't retry if an external wakeup count was supplied and the suspend
   // attempt failed due to a wakeup count mismatch -- a test probably triggered
   // this suspend attempt after setting a wake alarm, and if we retry later,
   // it's likely that the alarm will have already fired and the system will
   // never wake up.
-  const bool done = success ||
-      (got_external_wakeup_count_ && result == Delegate::SUSPEND_CANCELED);
-  const int old_suspend_id = suspend_id_;
-  const int old_num_attempts = num_attempts_;
-  if (done) {
-    num_attempts_ = 0;
-    if (success) {
-      LOG(INFO) << "Resumed successfully from suspend attempt " << suspend_id_;
-    } else {
-      LOG(WARNING) << "Giving up after canceled suspend attempt with external "
-                   << "wakeup count";
-    }
-  } else {
-    LOG(INFO) << "Suspend attempt " << suspend_id_ << " failed; "
-              << "will retry in " << retry_delay_.InMilliseconds() << " ms";
-    retry_suspend_timer_.Start(FROM_HERE, retry_delay_, this,
-                               &Suspender::RetrySuspend);
+  if ((result == Delegate::SUSPEND_SUCCESSFUL) ||
+      (result == Delegate::SUSPEND_CANCELED &&
+       suspend_request_supplied_wakeup_count_ && !in_dark_resume)) {
+    FinishRequest(result == Delegate::SUSPEND_SUCCESSFUL);
+    return STATE_IDLE;
   }
 
-  // Protect against the system clock having gone backwards.
-  base::TimeDelta elapsed_time = std::max(base::TimeDelta(),
-      clock_->GetCurrentWallTime() - start_wall_time);
-  AnnounceSuspendCompletion(old_suspend_id, elapsed_time);
-
-  // Check for bugs where another suspend attempt is started before the previous
-  // one is fully cleaned up.
-  DCHECK_EQ(suspend_id_, old_suspend_id)
-      << "Started new suspend attempt " << suspend_id_
-      << " while still cleaning up attempt " << old_suspend_id;
-
-  // Notify the delegate after all other cleanup is done; it may synchronously
-  // trigger another suspend attempt.
-  delegate_->HandleSuspendAttemptCompletion(success, old_num_attempts);
-}
-
-bool Suspender::ShutDownIfRetryLimitReached() {
-  if (num_attempts_ > max_retries_) {
-    LOG(ERROR) << "Unsuccessfully attempted to suspend " << num_attempts_
-               << " times; shutting down";
+  if (current_num_attempts_ > max_retries_) {
+    LOG(ERROR) << "Unsuccessfully attempted to suspend "
+               << current_num_attempts_ << " times; shutting down";
+    // Don't call FinishRequest(); we want the backlight to stay off.
     delegate_->ShutDownForFailedSuspend();
-    return true;
-  }
-  return false;
-}
-
-void Suspender::RetrySuspend() {
-  if (ShutDownIfRetryLimitReached())
-    return;
-
-  LOG(WARNING) << "Retry #" << num_attempts_;
-  StartSuspendAttempt(got_external_wakeup_count_ ? wakeup_count_ : 0,
-                      got_external_wakeup_count_);
-}
-
-void Suspender::CancelSuspend() {
-  if (waiting_for_readiness_) {
-    LOG(INFO) << "Canceling suspend before running powerd_suspend";
-    waiting_for_readiness_ = false;
-    DCHECK(!retry_suspend_timer_.IsRunning());
-    AnnounceSuspendCompletion(suspend_id_, base::TimeDelta());
-    delegate_->HandleCanceledSuspendAnnouncement();
-  } else if (retry_suspend_timer_.IsRunning()) {
-    LOG(INFO) << "Canceling suspend between retries";
-    retry_suspend_timer_.Stop();
+    return STATE_SHUTTING_DOWN;
   }
 
-  if (num_attempts_) {
-    delegate_->HandleCanceledSuspendRequest(num_attempts_);
-    num_attempts_ = 0;
-  }
+  LOG(WARNING) << "Suspend attempt #" << current_num_attempts_ << " failed; "
+               << "will retry in " << retry_delay_.InMilliseconds() << " ms";
+  if (!suspend_request_supplied_wakeup_count_ && !in_dark_resume)
+    wakeup_count_valid_ = delegate_->ReadSuspendWakeupCount(&wakeup_count_);
+  ScheduleResuspend(retry_delay_);
+  return STATE_WAITING_TO_RESUSPEND;
 }
 
-void Suspender::AnnounceSuspendCompletion(
-    int suspend_id,
-    const base::TimeDelta& suspend_duration) {
+void Suspender::ScheduleResuspend(const base::TimeDelta& delay) {
+  resuspend_timer_.Start(FROM_HERE, delay,
+      base::Bind(&Suspender::HandleEvent, base::Unretained(this),
+                 EVENT_READY_TO_RESUSPEND));
+}
+
+void Suspender::EmitSuspendImminentSignal(int suspend_request_id) {
+  SuspendImminent proto;
+  proto.set_suspend_id(suspend_request_id);
+  dbus_sender_->EmitSignalWithProtocolBuffer(kSuspendImminentSignal, proto);
+}
+
+void Suspender::EmitSuspendDoneSignal(int suspend_request_id,
+                                      const base::TimeDelta& suspend_duration) {
   SuspendDone proto;
-  proto.set_suspend_id(suspend_id);
+  proto.set_suspend_id(suspend_request_id);
   proto.set_suspend_duration(suspend_duration.ToInternalValue());
   dbus_sender_->EmitSignalWithProtocolBuffer(kSuspendDoneSignal, proto);
-  delegate_->SetSuspendAnnounced(false);
 }
 
 }  // namespace policy
diff --git a/powerd/policy/suspender.h b/powerd/policy/suspender.h
index 238c573..20487c0 100644
--- a/powerd/policy/suspender.h
+++ b/powerd/policy/suspender.h
@@ -5,6 +5,8 @@
 #ifndef POWER_MANAGER_POWERD_POLICY_SUSPENDER_H_
 #define POWER_MANAGER_POWERD_POLICY_SUSPENDER_H_
 
+#include <queue>
+
 #include <base/compiler_specific.h>
 #include <base/memory/scoped_ptr.h>
 #include <base/time/time.h>
@@ -30,44 +32,51 @@
 
 class SuspendDelayController;
 
-// Suspender is responsible for suspending the system.  The typical flow is
-// as follows:
+// Suspender is responsible for suspending the system.
+//
+// First, some terminology:
+//
+// - A "suspend request" refers to a request (either generated within powerd or
+//   originating from another process) for powerd to suspend the system. The
+//   request is complete after the system resumes successfully or the request is
+//   canceled (e.g. by user activity).
+//
+// - A "suspend attempt" refers to a single attempt by powerd to suspend the
+//   system by writing to /sys/power/state.
+//
+// A suspend request may result in multiple attempts: An attempt may fail and be
+// retried after a brief delay, or the system may do a "dark resume" (i.e. wake
+// without turning the display on), check the battery level, and then resuspend
+// immediately.
+//
+// The typical flow in the simple case is as follows:
 //
 // - RequestSuspend() is called when suspending is desired.
-// - SuspendDelayController emits a SuspendImminent signal to announce the new
-//   suspend request to processes that have previously registered suspend delays
-//   via RegisterSuspendDelay().
+// - StartRequest() does pre-suspend preparation and emits a SuspendImminent
+//   signal to announce the new suspend request to processes that have
+//   previously registered suspend delays via RegisterSuspendDelay().
 // - OnReadyForSuspend() is called to announce that all processes have announced
-//   readiness via HandleSuspendReadiness(). It calls Suspend(), which runs the
-//   powerd_suspend script to perform the actual suspend/resume cycle.
-// - After powerd_suspend returns, a SuspendDone signal is emitted. If
-//   powerd_suspend reported failure, a timeout is created to retry the suspend
+//   readiness via HandleSuspendReadiness().
+// - Suspend() runs the powerd_suspend script to perform a suspend attempt.
+// - After powerd_suspend returns successfully, FinishRequest() undoes the
+//   pre-suspend preparation and emits a SuspendDone signal.
+// - If powerd_suspend reported failure, a timer is started to retry the suspend
 //   attempt.
 //
 // At any point before Suspend() has been called, user activity can cancel the
-// current suspend attempt. A synthetic SuspendDone signal is emitted so that
-// other processes can undo any setup that they did in response to suspend
-// delays.
+// current suspend attempt.
 class Suspender : public SuspendDelayObserver {
  public:
   // Interface for classes responsible for performing actions on behalf of
   // Suspender.  The general sequence when suspending is:
   //
-  // - Suspender::RequestSuspend() calls PrepareForSuspendAnnouncement()
-  //   and then notifies other processes that the system is about to
-  //   suspend.
-  // - If the suspend attempt is canceled while Suspender is still waiting
-  //   for other processes to report readiness for suspend, then
-  //   HandleCanceledSuspendAnnouncement() is called.
-  // - Otherwise, PrepareForSuspend() is called.
-  // - Suspend() is then called within a do-while loop (looping only for dark
-  //   resumes).
-  // - After the system resumes from suspend (or if the suspend attempt
-  //   failed), HandleSuspendAttemptCompletion() is called.
-  // - If the suspend attempt failed, then the cycle will repeat, starting at
-  //   PrepareForSuspendAnnouncement().
-  // - If the suspend request is canceled due to user activity,
-  //   HandleCanceledSuspendRequest() will be called.
+  // - Suspender::StartRequest() calls PrepareToSuspend() and then notifies
+  //   other processes that the system is about to suspend.
+  // - Suspender::Suspend() calls DoSuspend() to actually suspend the system.
+  //   This may occur multiple times if the attempt fails and is retried or if
+  //   the system wakes for dark resume and then resuspends.
+  // - After the suspend request is complete, Suspender::FinishRequest()
+  //   calls UndoPrepareToSuspend().
   class Delegate {
    public:
     // Outcomes for a suspend attempt.
@@ -85,18 +94,18 @@
     // Returns a initial value for suspend-related IDs that's likely (but not
     // guaranteed) to yield successive IDs that are unique across all of the
     // powerd runs during the current boot session.
-    virtual int GetInitialId() = 0;
+    virtual int GetInitialSuspendId() = 0;
 
     // Is the lid currently closed?  Returns false if the query fails or if
     // the system doesn't have a lid.
-    virtual bool IsLidClosed() = 0;
+    virtual bool IsLidClosedForSuspend() = 0;
 
     // Reads the current wakeup count from sysfs and stores it in
     // |wakeup_count|. Returns true on success.
-    virtual bool GetWakeupCount(uint64* wakeup_count) = 0;
+    virtual bool ReadSuspendWakeupCount(uint64* wakeup_count) = 0;
 
     // Sets state that persists across powerd restarts but not across system
-    // reboots to track whether a suspend attempt's commencement was announced
+    // reboots to track whether a suspend requests's commencement was announced
     // (the SuspendImminent signal was emitted) but its completion wasn't (the
     // SuspendDone signal wasn't emitted).
     virtual void SetSuspendAnnounced(bool announced) = 0;
@@ -105,44 +114,22 @@
     virtual bool GetSuspendAnnounced() = 0;
 
     // Performs any work that needs to happen before other processes are
-    // informed that the system is about to suspend. Called by
-    // RequestSuspend().
-    virtual void PrepareForSuspendAnnouncement() = 0;
+    // informed that the system is about to suspend, including turning off the
+    // backlight and muting audio. Called by StartRequest().
+    virtual void PrepareToSuspend() = 0;
 
-    // Called if the suspend request is aborted before Suspend() and
-    // HandleSuspendAttemptCompletion() are called.  This method should undo any
-    // work done by PrepareForSuspendAnnouncement().
-    virtual void HandleCanceledSuspendAnnouncement() = 0;
+    // Synchronously runs the powerd_suspend script to suspend the system for
+    // |duration|. If |wakeup_count_valid| is true, passes |wakeup_count| to the
+    // script so it can avoid suspending if additional wakeup events occur.
+    // Called by Suspend().
+    virtual SuspendResult DoSuspend(uint64 wakeup_count,
+                                    bool wakeup_count_valid,
+                                    base::TimeDelta duration) = 0;
 
-    // Handles putting the system into the correct state before suspending such
-    // as suspending the backlight and muting audio. This is separate from
-    // Suspend() since for the purpose of a dark resume (which can call suspend
-    // again), we don't want to touch the state of the backlight or audio.
-    virtual void PrepareForSuspend() = 0;
-
-    // Synchronously runs the powerd_suspend script to suspend the system.
-    // If |wakeup_count_valid| is true, passes |wakeup_count| to the script
-    // so it can avoid suspending if additional wakeup events occur.  After
-    // the suspend/resume cycle is complete (and even if the system failed
-    // to suspend), HandleSuspendAttemptCompletion() will be called.
-    virtual SuspendResult Suspend(uint64 wakeup_count,
-                                  bool wakeup_count_valid,
-                                  base::TimeDelta duration) = 0;
-
-    // Handles the system resuming or recovering from a failed suspend
-    // attempt. |num_suspend_attempts| contains the number of suspend attempts
-    // that have been made so far (i.e. the initial attempt plus any retries).
-    // This method should undo any work done by both
-    // PrepareForSuspendAnnouncement() and PrepareForSuspend().
-    virtual void HandleSuspendAttemptCompletion(bool suspend_was_successful,
-                                                int num_suspend_attempts) = 0;
-
-    // Called when a suspend request (that is, a series of suspend attempts
-    // being performed in response to an initial call to RequestSuspend(),
-    // rather than an individual suspend attempt) has been canceled.
-    // |num_suspend_attempts| contains the number of individual attempts that
-    // had been made.
-    virtual void HandleCanceledSuspendRequest(int num_suspend_attempts) = 0;
+    // Undoes the preparations performed by PrepareToSuspend(). Called by
+    // FinishRequest().
+    virtual void UndoPrepareToSuspend(bool success,
+                                      int num_suspend_attempts) = 0;
 
     // Shuts the system down in response to repeated failed suspend attempts.
     virtual void ShutDownForFailedSuspend() = 0;
@@ -157,14 +144,14 @@
    public:
     explicit TestApi(Suspender* suspender);
 
-    int suspend_id() const { return suspender_->suspend_id_; }
+    int suspend_id() const { return suspender_->suspend_request_id_; }
 
     // Sets the time used as "now".
     void SetCurrentWallTime(base::Time wall_time);
 
-    // Runs Suspender::RetrySuspend() if |retry_suspend_timer_| is running.
-    // Returns false otherwise.
-    bool TriggerRetryTimeout();
+    // Runs Suspender::HandleEvent(EVENT_READY_TO_RESUSPEND) if
+    // |resuspend_timer_| is running. Returns false otherwise.
+    bool TriggerResuspendTimeout();
 
    private:
     Suspender* suspender_;  // weak
@@ -222,32 +209,58 @@
   virtual void OnReadyForSuspend(int suspend_id) OVERRIDE;
 
  private:
-  // Notifies clients that have registered delays that the system is about
-  // to suspend. Internal implementation shared by RequestSuspend() and
-  // RequestSuspendWithExternalWakeupCount().
-  void StartSuspendAttempt(uint64 external_wakeup_count,
-                           bool use_external_wakeup_count);
+  // States that Suspender can be in while the event loop is running.
+  enum State {
+    // Nothing suspend-related is going on.
+    STATE_IDLE = 0,
+    // powerd has announced a new suspend request to other processes and is
+    // waiting for clients that have registered suspend delays to report
+    // readiness.
+    STATE_WAITING_FOR_SUSPEND_DELAYS,
+    // powerd is waiting to resuspend after a failed suspend attempt or after
+    // waking into dark resume.
+    STATE_WAITING_TO_RESUSPEND,
+    // The system is shutting down. Suspend requests are ignored.
+    STATE_SHUTTING_DOWN,
+  };
 
-  // Actually suspends the system. Before this method is called, the system
-  // should be in a state where it's truly ready to suspend (i.e. no
-  // outstanding delays).
-  void Suspend();
+  enum Event {
+    // A suspend request was received.
+    EVENT_SUSPEND_REQUESTED = 0,
+    // Clients that have registered suspend delays have all reported readiness
+    // (or timed out).
+    EVENT_SUSPEND_DELAYS_READY,
+    // User activity was reported.
+    EVENT_USER_ACTIVITY,
+    // The system is ready to resuspend (after either a failed suspend attempt
+    // or a dark resume).
+    EVENT_READY_TO_RESUSPEND,
+    // The system is shutting down.
+    EVENT_SHUTDOWN_STARTED,
+  };
 
-  // Shuts the system down and returns true if |num_attempts_| exceeds
-  // |max_retries_|.
-  bool ShutDownIfRetryLimitReached();
+  // Performs actions and updates |state_| in response to |event|.
+  void HandleEvent(Event event);
 
-  // Retries a suspend attempt.
-  void RetrySuspend();
+  // Starts a new suspend request, notifying clients that have registered delays
+  // that the system is about to suspend.
+  void StartRequest();
 
-  // Cancels an outstanding suspend request.
-  void CancelSuspend();
+  // Completes the current suspend request, undoing any work performed by
+  // StartRequest().
+  void FinishRequest(bool success);
 
-  // Announces to other processes that attempt |suspend_id| completed in
-  // |suspend_duration|. Emits a SuspendDone D-Bus signal and calls
-  // delegate_->SetSuspendAnnounced(false).
-  void AnnounceSuspendCompletion(int suspend_id,
-                                 const base::TimeDelta& suspend_duration);
+  // Actually suspends the system and returns a new value for |state_| after
+  // performing any work needed to put the system into this state.
+  State Suspend();
+
+  // Starts |resuspend_timer_| to send EVENT_READY_TO_RESUSPEND after |delay|.
+  void ScheduleResuspend(const base::TimeDelta& delay);
+
+  // Emits D-Bus signals announcing the beginning or end of a suspend attempt.
+  void EmitSuspendImminentSignal(int suspend_request_id);
+  void EmitSuspendDoneSignal(int suspend_request_id,
+                             const base::TimeDelta& suspend_duration);
 
   Delegate* delegate_;  // weak
   DBusSenderInterface* dbus_sender_;  // weak
@@ -256,23 +269,35 @@
   scoped_ptr<Clock> clock_;
   scoped_ptr<SuspendDelayController> suspend_delay_controller_;
 
-  // Whether the system will be suspended soon.  This is set to true by
-  // RequestSuspend() and set to false by OnReadyForSuspend().
-  bool waiting_for_readiness_;
+  // Current state of the object, updated just before returning control to the
+  // event loop.
+  State state_;
 
-  // Unique ID associated with the current suspend attempt.
-  int suspend_id_;
+  // True if HandleEvent() is currently handling an event.
+  bool handling_event_;
 
-  // Number of wakeup events received at start of the current suspend attempt.
+  // True if HandleEvent() is currently processing |queued_events_|.
+  bool processing_queued_events_;
+
+  // Unhandled events that were received while |handling_event_| was true.
+  std::queue<Event> queued_events_;
+
+  // Unique ID associated with the current suspend request.
+  int suspend_request_id_;
+
+  // An optional wakeup count supplied via
+  // RequestSuspendWithExternalWakeupCount().
+  bool suspend_request_supplied_wakeup_count_;
+  uint64 suspend_request_wakeup_count_;
+
+  // Number of wakeup events received at the start of the current suspend
+  // attempt. Passed to the kernel to cancel an attempt if user activity is
+  // received while powerd's event loop isn't running.
   uint64 wakeup_count_;
   bool wakeup_count_valid_;
 
-  // Did |wakeup_count_| come from an external process via
-  // RequestSuspendWithExternalWakeupCount()?
-  bool got_external_wakeup_count_;
-
-  // The duration the machine should suspend for.
-  int64 suspend_duration_;
+  // Wall time at which the suspend request started.
+  base::Time suspend_request_start_time_;
 
   // Time to wait before retrying a failed suspend attempt.
   base::TimeDelta retry_delay_;
@@ -281,15 +306,18 @@
   // giving up and shutting down the system.
   int64 max_retries_;
 
-  // Number of suspend attempts made since the initial RequestSuspend() call.
-  int num_attempts_;
+  // Number of suspend attempts made in the current series. Up to |max_retries_|
+  // additional attempts are made after a failure, but this counter is reset
+  // after waking into dark resume.
+  int current_num_attempts_;
 
-  // Runs RetrySuspend().
-  base::OneShotTimer<Suspender> retry_suspend_timer_;
+  // Number of suspend attempts made in the first series after the
+  // RequestSuspend() call. |current_num_attempts_| is copied here when doing a
+  // dark resume.
+  int initial_num_attempts_;
 
-  // True after this class has received notification that the system is
-  // shutting down.
-  bool shutting_down_;
+  // Runs HandleEvent(EVENT_READY_TO_RESUSPEND).
+  base::OneShotTimer<Suspender> resuspend_timer_;
 
   DISALLOW_COPY_AND_ASSIGN(Suspender);
 };
diff --git a/powerd/policy/suspender_unittest.cc b/powerd/policy/suspender_unittest.cc
index 2f86afb..eb28c09 100644
--- a/powerd/policy/suspender_unittest.cc
+++ b/powerd/policy/suspender_unittest.cc
@@ -25,12 +25,9 @@
 namespace {
 
 // Various actions that can be returned by TestDelegate::GetActions().
-const char kAnnounce[] = "announce";
 const char kPrepare[] = "prepare";
-const char kCancel[] = "cancel";
 const char kSuspend[] = "suspend";
-const char kAttemptComplete[] = "attempt_complete";
-const char kRequestCanceled[] = "request_canceled";
+const char kUnprepare[] = "unprepare";
 const char kShutDown[] = "shut_down";
 const char kNoActions[] = "";
 
@@ -40,7 +37,7 @@
  public:
   TestDelegate()
       : lid_closed_(false),
-        report_success_for_get_wakeup_count_(true),
+        report_success_for_read_wakeup_count_(true),
         suspend_result_(SUSPEND_SUCCESSFUL),
         wakeup_count_(0),
         suspend_announced_(false),
@@ -51,8 +48,8 @@
   }
 
   void set_lid_closed(bool closed) { lid_closed_ = closed; }
-  void set_report_success_for_get_wakeup_count(bool success) {
-    report_success_for_get_wakeup_count_ = success;
+  void set_report_success_for_read_wakeup_count(bool success) {
+    report_success_for_read_wakeup_count_ = success;
   }
   void set_suspend_announced(bool announced) { suspend_announced_ = announced; }
   void set_suspend_result(SuspendResult result) {
@@ -65,22 +62,26 @@
   void set_completion_callback(base::Closure callback) {
     completion_callback_ = callback;
   }
+  void set_shutdown_callback(base::Closure callback) {
+    shutdown_callback_ = callback;
+  }
 
   bool suspend_announced() const { return suspend_announced_; }
   uint64 suspend_wakeup_count() const { return suspend_wakeup_count_; }
   bool suspend_wakeup_count_valid() const {
     return suspend_wakeup_count_valid_;
   }
+  const base::TimeDelta& suspend_duration() const { return suspend_duration_; }
   bool suspend_was_successful() const { return suspend_was_successful_; }
   int num_suspend_attempts() const { return num_suspend_attempts_; }
 
   // Delegate implementation:
-  virtual int GetInitialId() OVERRIDE { return 1; }
+  virtual int GetInitialSuspendId() OVERRIDE { return 1; }
 
-  virtual bool IsLidClosed() OVERRIDE { return lid_closed_; }
+  virtual bool IsLidClosedForSuspend() OVERRIDE { return lid_closed_; }
 
-  virtual bool GetWakeupCount(uint64* wakeup_count) OVERRIDE {
-    if (!report_success_for_get_wakeup_count_)
+  virtual bool ReadSuspendWakeupCount(uint64* wakeup_count) OVERRIDE {
+    if (!report_success_for_read_wakeup_count_)
       return false;
     *wakeup_count = wakeup_count_;
     return true;
@@ -92,85 +93,79 @@
 
   virtual bool GetSuspendAnnounced() OVERRIDE { return suspend_announced_; }
 
-  virtual void PrepareForSuspendAnnouncement() OVERRIDE {
-    AppendAction(kAnnounce);
+  virtual void PrepareToSuspend() OVERRIDE {
+    AppendAction(kPrepare);
   }
 
-  virtual void HandleCanceledSuspendAnnouncement() OVERRIDE {
-    AppendAction(kCancel);
-  }
-
-  virtual SuspendResult Suspend(uint64 wakeup_count,
-                                bool wakeup_count_valid,
-                                base::TimeDelta duration) OVERRIDE {
+  virtual SuspendResult DoSuspend(uint64 wakeup_count,
+                                  bool wakeup_count_valid,
+                                  base::TimeDelta duration) OVERRIDE {
     AppendAction(kSuspend);
     suspend_wakeup_count_ = wakeup_count;
     suspend_wakeup_count_valid_ = wakeup_count_valid;
     suspend_duration_ = duration;
-    if (!suspend_callback_.is_null()) {
-      base::Closure cb = suspend_callback_;
-      suspend_callback_.Reset();
-      cb.Run();
-    }
+    RunAndResetCallback(&suspend_callback_);
     return suspend_result_;
   }
 
-  virtual void PrepareForSuspend() { AppendAction(kPrepare); }
-
-  virtual void HandleSuspendAttemptCompletion(
-      bool success,
-      int num_suspend_attempts) OVERRIDE {
-    AppendAction(kAttemptComplete);
+  virtual void UndoPrepareToSuspend(bool success,
+                                    int num_suspend_attempts) OVERRIDE {
+    AppendAction(kUnprepare);
     suspend_was_successful_ = success;
     num_suspend_attempts_ = num_suspend_attempts;
-    if (!completion_callback_.is_null()) {
-      base::Closure cb = completion_callback_;
-      completion_callback_.Reset();
-      cb.Run();
-    }
-  }
-
-  virtual void HandleCanceledSuspendRequest(int num_suspend_attempts) OVERRIDE {
-    AppendAction(kRequestCanceled);
-    num_suspend_attempts_ = num_suspend_attempts;
+    RunAndResetCallback(&completion_callback_);
   }
 
   virtual void ShutDownForFailedSuspend() OVERRIDE {
     AppendAction(kShutDown);
+    RunAndResetCallback(&shutdown_callback_);
   }
 
-  virtual void ShutDownForDarkResume() OVERRIDE { AppendAction(kShutDown); }
+  virtual void ShutDownForDarkResume() OVERRIDE {
+    AppendAction(kShutDown);
+    RunAndResetCallback(&shutdown_callback_);
+  }
 
  private:
-  // Value returned by IsLidClosed().
+  // If |callback| is non-null, runs and resets it.
+  static void RunAndResetCallback(base::Closure* callback) {
+    if (callback->is_null())
+      return;
+
+    base::Closure callback_copy = *callback;
+    callback->Reset();
+    callback_copy.Run();
+  }
+
+  // Value returned by IsLidClosedForSuspend().
   bool lid_closed_;
 
-  // Should GetWakeupCount() and Suspend() report success?
-  bool report_success_for_get_wakeup_count_;
+  // Should ReadSuspendWakeupCount() and DoSuspend() report success?
+  bool report_success_for_read_wakeup_count_;
   SuspendResult suspend_result_;
 
-  // Count that should be returned by GetWakeupCount().
+  // Count that should be returned by ReadSuspendWakeupCount().
   uint64 wakeup_count_;
 
-  // Value updated by SetSuspendAnnounced() and returned by
-  // GetSuspendAnnounced().
+  // Updated by SetSuspendAnnounced() and returned by GetSuspendAnnounced().
   bool suspend_announced_;
 
-  // Callback that will be run once (if non-null) when Suspend() is called.
+  // Callback that will be run once (if non-null) when DoSuspend() is called.
   base::Closure suspend_callback_;
 
   // Callback that will be run once (if non-null) when
-  // HandleSuspendAttemptCompletion() is called.
+  // UndoPrepareToSuspend() is called.
   base::Closure completion_callback_;
 
-  // Arguments passed to latest invocation of Suspend().
+  // Callback that will be run once (if non-null) when ShutDown*() is called.
+  base::Closure shutdown_callback_;
+
+  // Arguments passed to last invocation of DoSuspend().
   uint64 suspend_wakeup_count_;
   bool suspend_wakeup_count_valid_;
   base::TimeDelta suspend_duration_;
 
-  // Arguments passed to last invocation of HandleSuspendAttemptCompletion().
-  // |num_suspend_attempts_| is also updated in response to
-  // HandleCanceledSuspendRequest().
+  // Arguments passed to last invocation of UndoPrepareToSuspend().
   bool suspend_was_successful_;
   int num_suspend_attempts_;
 
@@ -194,6 +189,24 @@
     suspender_.Init(&delegate_, &dbus_sender_, &dark_resume_, &prefs_);
   }
 
+  // Returns the ID from a SuspendImminent signal at |position|, or -1 if the
+  // signal wasn't sent.
+  int GetSuspendImminentId(int position) {
+    SuspendImminent proto;
+    if (!dbus_sender_.GetSentSignal(position, kSuspendImminentSignal, &proto))
+      return -1;
+    return proto.suspend_id();
+  }
+
+  // Returns the ID from a SuspendDone signal at |position|, or -1 if the signal
+  // wasn't sent.
+  int GetSuspendDoneId(int position) {
+    SuspendDone proto;
+    if (!dbus_sender_.GetSentSignal(position, kSuspendDoneSignal, &proto))
+      return -1;
+    return proto.suspend_id();
+  }
+
   FakePrefs prefs_;
   TestDelegate delegate_;
   DBusSenderStub dbus_sender_;
@@ -217,11 +230,8 @@
   delegate_.set_wakeup_count(kWakeupCount);
   suspender_.RequestSuspend();
   const int suspend_id = test_api_.suspend_id();
-  SuspendImminent imminent_proto;
-  EXPECT_TRUE(
-      dbus_sender_.GetSentSignal(0, kSuspendImminentSignal, &imminent_proto));
-  EXPECT_EQ(suspend_id, imminent_proto.suspend_id());
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   EXPECT_TRUE(delegate_.suspend_announced());
 
   // Advance the time and register a callback to advance the time again
@@ -237,8 +247,7 @@
   // suspended, it should immediately suspend the system.
   dbus_sender_.ClearSentSignals();
   suspender_.OnReadyForSuspend(suspend_id);
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
   EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
   EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
   EXPECT_TRUE(delegate_.suspend_was_successful());
@@ -249,12 +258,12 @@
   SuspendDone done_proto;
   EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
   EXPECT_EQ(suspend_id, done_proto.suspend_id());
-  EXPECT_EQ((kResumeTime - kSuspendTime).ToInternalValue(),
+  EXPECT_EQ((kResumeTime - kRequestTime).ToInternalValue(),
             done_proto.suspend_duration());
   EXPECT_FALSE(delegate_.suspend_announced());
 
-  // A retry timeout shouldn't be set.
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  // A resuspend timeout shouldn't be set.
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 }
 
 // Tests that Suspender doesn't pass a wakeup count to the delegate when it was
@@ -262,12 +271,11 @@
 TEST_F(SuspenderTest, MissingWakeupCount) {
   Init();
 
-  delegate_.set_report_success_for_get_wakeup_count(false);
+  delegate_.set_report_success_for_read_wakeup_count(false);
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
   EXPECT_FALSE(delegate_.suspend_wakeup_count_valid());
 }
 
@@ -277,8 +285,8 @@
   Init();
 
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
-  int orig_suspend_id = test_api_.suspend_id();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  const int orig_suspend_id = test_api_.suspend_id();
 
   // The suspend ID should be left unchanged after a second call to
   // RequestSuspend().
@@ -295,41 +303,26 @@
   delegate_.set_wakeup_count(kOrigWakeupCount);
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
-  int orig_suspend_id = test_api_.suspend_id();
+  const int suspend_id = test_api_.suspend_id();
+  EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   EXPECT_TRUE(delegate_.suspend_announced());
 
-  dbus_sender_.ClearSentSignals();
-  suspender_.OnReadyForSuspend(orig_suspend_id);
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
-  EXPECT_EQ(kOrigWakeupCount, delegate_.suspend_wakeup_count());
-  EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
-  EXPECT_FALSE(delegate_.suspend_was_successful());
-  EXPECT_EQ(1, delegate_.num_suspend_attempts());
-  SuspendDone done_proto;
-  EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
-  EXPECT_EQ(orig_suspend_id, done_proto.suspend_id());
-  EXPECT_FALSE(delegate_.suspend_announced());
-
-  // The timeout should trigger a new suspend announcement.
   const uint64 kRetryWakeupCount = 67;
   delegate_.set_wakeup_count(kRetryWakeupCount);
-  EXPECT_TRUE(test_api_.TriggerRetryTimeout());
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
-  int new_suspend_id = test_api_.suspend_id();
-  EXPECT_NE(orig_suspend_id, new_suspend_id);
-
   dbus_sender_.ClearSentSignals();
-  suspender_.OnReadyForSuspend(new_suspend_id);
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  suspender_.OnReadyForSuspend(suspend_id);
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+  EXPECT_EQ(kOrigWakeupCount, delegate_.suspend_wakeup_count());
+  EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
+  EXPECT_EQ(0, dbus_sender_.num_sent_signals());
+
+  // The timeout should trigger another suspend attempt.
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
   EXPECT_EQ(kRetryWakeupCount, delegate_.suspend_wakeup_count());
   EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
-  EXPECT_FALSE(delegate_.suspend_was_successful());
-  EXPECT_EQ(2, delegate_.num_suspend_attempts());
-  EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
-  EXPECT_EQ(new_suspend_id, done_proto.suspend_id());
+  EXPECT_EQ(0, dbus_sender_.num_sent_signals());
 
   // A second suspend request should be ignored so we'll avoid trying to
   // re-suspend immediately if an attempt fails while the lid is closed
@@ -338,34 +331,33 @@
   const uint64 kExternalWakeupCount = 32542;
   suspender_.RequestSuspendWithExternalWakeupCount(kExternalWakeupCount);
   EXPECT_EQ(kNoActions, delegate_.GetActions());
+  EXPECT_EQ(0, dbus_sender_.num_sent_signals());
 
-  // Report success this time and check that the timeout isn't registered.
-  EXPECT_TRUE(test_api_.TriggerRetryTimeout());
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
-  int final_suspend_id = test_api_.suspend_id();
-  EXPECT_NE(new_suspend_id, final_suspend_id);
-  dbus_sender_.ClearSentSignals();
+  // Report success this time and check that the timer isn't running.
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
-  suspender_.OnReadyForSuspend(final_suspend_id);
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
   EXPECT_NE(kExternalWakeupCount, delegate_.suspend_wakeup_count());
   EXPECT_TRUE(delegate_.suspend_was_successful());
   EXPECT_EQ(3, delegate_.num_suspend_attempts());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
-  EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
-  EXPECT_EQ(final_suspend_id, done_proto.suspend_id());
+  EXPECT_EQ(suspend_id, GetSuspendDoneId(0));
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 
   // Suspend successfully again and check that the number of attempts are
   // reported as 1 now.
+  dbus_sender_.ClearSentSignals();
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
-  suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  const int new_suspend_id = test_api_.suspend_id();
+  EXPECT_EQ(new_suspend_id, GetSuspendImminentId(0));
+
+  dbus_sender_.ClearSentSignals();
+  suspender_.OnReadyForSuspend(new_suspend_id);
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
   EXPECT_TRUE(delegate_.suspend_was_successful());
   EXPECT_EQ(1, delegate_.num_suspend_attempts());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  EXPECT_EQ(new_suspend_id, GetSuspendDoneId(0));
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 }
 
 // Tests that the system is shut down after repeated suspend failures.
@@ -375,23 +367,14 @@
 
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
-  EXPECT_FALSE(delegate_.suspend_was_successful());
-  EXPECT_EQ(1, delegate_.num_suspend_attempts());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
 
   // Proceed through all retries, reporting failure each time.
-  for (int i = 1; i <= pref_num_retries_; ++i) {
-    EXPECT_TRUE(test_api_.TriggerRetryTimeout()) << "Retry #" << i;
-    EXPECT_EQ(kAnnounce, delegate_.GetActions()) << "Retry #" << i;
-    suspender_.OnReadyForSuspend(test_api_.suspend_id());
-    EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-              delegate_.GetActions())
-        << "Retry #" << i;
-    EXPECT_FALSE(delegate_.suspend_was_successful());
-    EXPECT_EQ(i + 1, delegate_.num_suspend_attempts());
+  for (int i = 1; i <= pref_num_retries_ - 1; ++i) {
+    EXPECT_TRUE(test_api_.TriggerResuspendTimeout()) << "Retry #" << i;
+    EXPECT_EQ(kSuspend, delegate_.GetActions()) << "Retry #" << i;
   }
 
   // Check that another suspend request doesn't reset the retry count
@@ -399,10 +382,10 @@
   suspender_.RequestSuspend();
   EXPECT_EQ(kNoActions, delegate_.GetActions());
 
-  // On the last failed attempt, the system should shut down immediately.
-  EXPECT_TRUE(test_api_.TriggerRetryTimeout());
-  EXPECT_EQ(kShutDown, delegate_.GetActions());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  // After the last failed attempt, the system should shut down immediately.
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(JoinActions(kSuspend, kShutDown, NULL), delegate_.GetActions());
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 }
 
 // Tests that OnReadyForSuspend() doesn't trigger a call to Suspend() if
@@ -413,48 +396,48 @@
 
   // User activity should cancel suspending.
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
   EXPECT_TRUE(delegate_.suspend_announced());
-  dbus_sender_.ClearSentSignals();
-  suspender_.HandleUserActivity();
 
-  SuspendDone done_proto;
-  EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
-  EXPECT_EQ(test_api_.suspend_id(), done_proto.suspend_id());
-  EXPECT_EQ(base::TimeDelta().ToInternalValue(), done_proto.suspend_duration());
+  suspender_.HandleUserActivity();
+  EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
   EXPECT_FALSE(delegate_.suspend_announced());
-  EXPECT_EQ(JoinActions(kCancel, kRequestCanceled, NULL),
-            delegate_.GetActions());
-  EXPECT_EQ(1, delegate_.num_suspend_attempts());
+  EXPECT_EQ(kUnprepare, delegate_.GetActions());
+  EXPECT_FALSE(delegate_.suspend_was_successful());
+  EXPECT_EQ(0, delegate_.num_suspend_attempts());
+
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
   EXPECT_EQ(kNoActions, delegate_.GetActions());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 
   // The lid being opened should also cancel.
-  suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
   dbus_sender_.ClearSentSignals();
+  suspender_.RequestSuspend();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
   suspender_.HandleLidOpened();
-  EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
-  EXPECT_EQ(JoinActions(kCancel, kRequestCanceled, NULL),
-            delegate_.GetActions());
-  EXPECT_EQ(1, delegate_.num_suspend_attempts());
+  EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
+  EXPECT_EQ(kUnprepare, delegate_.GetActions());
+  EXPECT_FALSE(delegate_.suspend_was_successful());
+  EXPECT_EQ(0, delegate_.num_suspend_attempts());
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
   EXPECT_EQ(kNoActions, delegate_.GetActions());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 
   // The request should also be canceled if the system starts shutting down.
-  suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
   dbus_sender_.ClearSentSignals();
+  suspender_.RequestSuspend();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
   suspender_.HandleShutdown();
-  EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
-  EXPECT_EQ(JoinActions(kCancel, kRequestCanceled, NULL),
-            delegate_.GetActions());
-  EXPECT_EQ(1, delegate_.num_suspend_attempts());
+  EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
+  EXPECT_EQ(kUnprepare, delegate_.GetActions());
+  EXPECT_FALSE(delegate_.suspend_was_successful());
+  EXPECT_EQ(0, delegate_.num_suspend_attempts());
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
   EXPECT_EQ(kNoActions, delegate_.GetActions());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 
   // Subsequent requests after shutdown has started should be ignored.
   suspender_.RequestSuspend();
@@ -467,27 +450,20 @@
   Init();
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
-  EXPECT_FALSE(delegate_.suspend_was_successful());
-  EXPECT_EQ(1, delegate_.num_suspend_attempts());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
 
   // Fail a second time.
-  EXPECT_TRUE(test_api_.TriggerRetryTimeout());
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
-  suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+
+  // This time, report user activity first, which should cancel the request.
+  suspender_.HandleUserActivity();
+  EXPECT_EQ(kUnprepare, delegate_.GetActions());
   EXPECT_FALSE(delegate_.suspend_was_successful());
   EXPECT_EQ(2, delegate_.num_suspend_attempts());
-
-  // This time, also report user activity, which should cancel the request.
-  suspender_.HandleUserActivity();
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
-  EXPECT_EQ(kRequestCanceled, delegate_.GetActions());
-  EXPECT_EQ(2, delegate_.num_suspend_attempts());
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 }
 
 // Tests that Chrome-reported user activity received while suspending with
@@ -499,28 +475,23 @@
   // Report user activity before powerd_suspend is executed and check that
   // Suspender still suspends when OnReadyForSuspend() is called.
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   suspender_.HandleUserActivity();
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
 
   // Report user activity after powerd_suspend fails and check that the
-  // retry timeout isn't removed.
+  // resuspend timer isn't stopped.
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_CANCELED);
   suspender_.RequestSuspend();
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
   suspender_.HandleUserActivity();
 
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
-  EXPECT_TRUE(test_api_.TriggerRetryTimeout());
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
-  suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
 }
 
 // Tests that expected wakeup counts passed to
@@ -532,37 +503,35 @@
   const uint64 kWakeupCount = 452;
   delegate_.set_wakeup_count(kWakeupCount);
   suspender_.RequestSuspendWithExternalWakeupCount(kWakeupCount - 1);
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
 
   // Make the delegate report that powerd_suspend reported a wakeup count
   // mismatch. Suspender should avoid retrying after a mismatch when using an
   // external wakeup count.
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_CANCELED);
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
   EXPECT_EQ(kWakeupCount - 1, delegate_.suspend_wakeup_count());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  EXPECT_FALSE(delegate_.suspend_was_successful());
+  EXPECT_EQ(1, delegate_.num_suspend_attempts());
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 
   // Send another suspend request with the current wakeup count. Report failure
-  // and check that the suspend attempt is retried.
+  // and check that the suspend attempt is retried using the external wakeup
+  // count.
   suspender_.RequestSuspendWithExternalWakeupCount(kWakeupCount);
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
   suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
   EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
 
   // Let the retry succeed and check that another retry isn't scheduled.
-  EXPECT_TRUE(test_api_.TriggerRetryTimeout());
-  EXPECT_EQ(kAnnounce, delegate_.GetActions());
   delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
-  suspender_.OnReadyForSuspend(test_api_.suspend_id());
-  EXPECT_EQ(JoinActions(kPrepare, kSuspend, kAttemptComplete, NULL),
-            delegate_.GetActions());
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
   EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
-  EXPECT_FALSE(test_api_.TriggerRetryTimeout());
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
 }
 
 // Tests that the SuspendDone signal contains a zero duration rather than a
@@ -570,9 +539,9 @@
 // resume.
 TEST_F(SuspenderTest, SystemClockGoesBackward) {
   Init();
+  test_api_.SetCurrentWallTime(base::Time::FromInternalValue(5000));
   suspender_.RequestSuspend();
 
-  test_api_.SetCurrentWallTime(base::Time::FromInternalValue(5000));
   delegate_.set_suspend_callback(
       base::Bind(&Suspender::TestApi::SetCurrentWallTime,
                  base::Unretained(&test_api_),
@@ -585,26 +554,47 @@
 }
 
 // Tests that things don't go haywire when
-// Suspender::Delegate::HandleSuspendAttemptCompletion() synchronously requests
-// another suspend attempt. Previously, this could result in the new suspend
-// attempt being started before the previous one had completed.
-TEST_F(SuspenderTest, CompletionCallbackStartsNewAttempt) {
-  // Instruct the delegate to report failure and to start another suspend
-  // attempt when the current one finishes.
+// Suspender::Delegate::UndoPrepareToSuspend() synchronously starts another
+// suspend request. Previously, this could result in the new request being
+// started before the previous one had completed.
+TEST_F(SuspenderTest, EventReceivedWhileHandlingEvent) {
+  // Instruct the delegate to send another suspend request when the current one
+  // finishes.
   Init();
   suspender_.RequestSuspend();
-  delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
   delegate_.set_completion_callback(
       base::Bind(&Suspender::RequestSuspend, base::Unretained(&suspender_)));
 
-  // Check that the SuspendDone signal from the first attempt contains the first
-  // attempt's ID.
+  // Check that the SuspendDone signal from the first request contains the first
+  // request's ID, and that a second request was started immediately.
   dbus_sender_.ClearSentSignals();
   const int kOldSuspendId = test_api_.suspend_id();
   suspender_.OnReadyForSuspend(kOldSuspendId);
-  SuspendDone done_proto;
-  EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
-  EXPECT_EQ(kOldSuspendId, done_proto.suspend_id());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, kPrepare, NULL),
+            delegate_.GetActions());
+  EXPECT_EQ(kOldSuspendId, GetSuspendDoneId(0));
+  const int kNewSuspendId = test_api_.suspend_id();
+  EXPECT_NE(kOldSuspendId, kNewSuspendId);
+  EXPECT_EQ(kNewSuspendId, GetSuspendImminentId(1));
+
+  // Finish the second request.
+  dbus_sender_.ClearSentSignals();
+  suspender_.OnReadyForSuspend(kNewSuspendId);
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
+  EXPECT_EQ(kNewSuspendId, GetSuspendDoneId(0));
+  dbus_sender_.ClearSentSignals();
+
+  // Now make the delegate's shutdown method report that the system is shutting
+  // down.
+  delegate_.set_shutdown_callback(
+      base::Bind(&Suspender::HandleShutdown, base::Unretained(&suspender_)));
+  suspender_.RequestSuspend();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  dark_resume_.set_action(system::DarkResumeInterface::SHUT_DOWN);
+  suspender_.OnReadyForSuspend(test_api_.suspend_id());
+  EXPECT_EQ(kShutDown, delegate_.GetActions());
 }
 
 // Tests that a SuspendDone signal is emitted at startup and the "suspend
@@ -620,5 +610,116 @@
   EXPECT_FALSE(delegate_.suspend_announced());
 }
 
+TEST_F(SuspenderTest, DarkResume) {
+  Init();
+  const int kWakeupCount = 45;
+  delegate_.set_wakeup_count(kWakeupCount);
+  suspender_.RequestSuspend();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  const int kSuspendId = test_api_.suspend_id();
+  EXPECT_EQ(kSuspendId, GetSuspendImminentId(0));
+
+  // Instruct |dark_resume_| to request a ten-second suspend and report that the
+  // system did a dark resume.
+  const int64 kSuspendSec = 10;
+  dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
+  dark_resume_.set_in_dark_resume(true);
+  dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(kSuspendSec));
+  dbus_sender_.ClearSentSignals();
+  suspender_.OnReadyForSuspend(kSuspendId);
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+  EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
+  EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
+  EXPECT_EQ(kSuspendSec, delegate_.suspend_duration().InSeconds());
+  EXPECT_EQ(0, dbus_sender_.num_sent_signals());
+
+  // The system should resuspend for another ten seconds, but without using the
+  // wakeup count this time. Make it do a normal resume.
+  dark_resume_.set_in_dark_resume(false);
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
+  EXPECT_FALSE(delegate_.suspend_wakeup_count_valid());
+  EXPECT_EQ(kSuspendSec, delegate_.suspend_duration().InSeconds());
+  EXPECT_EQ(kSuspendId, GetSuspendDoneId(0));
+
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
+}
+
+TEST_F(SuspenderTest, DarkResumeShutDown) {
+  Init();
+  suspender_.RequestSuspend();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+  dark_resume_.set_action(system::DarkResumeInterface::SHUT_DOWN);
+  suspender_.OnReadyForSuspend(test_api_.suspend_id());
+  EXPECT_EQ(kShutDown, delegate_.GetActions());
+}
+
+TEST_F(SuspenderTest, DarkResumeRetry) {
+  pref_num_retries_ = 2;
+  Init();
+  suspender_.RequestSuspend();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+
+  // Suspend for ten seconds.
+  const int64 kSuspendSec = 10;
+  dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
+  dark_resume_.set_in_dark_resume(true);
+  dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(kSuspendSec));
+  suspender_.OnReadyForSuspend(test_api_.suspend_id());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+
+  // Now make one resuspend attempt while in dark resume fail and a second
+  // attempt succeed. The successful attempt should reset the retry counter.
+  delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+  delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+
+  // Fail to resuspend one time short of the retry limit.
+  delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
+  for (int i = 0; i < pref_num_retries_; ++i) {
+    SCOPED_TRACE(base::StringPrintf("Attempt #%d", i));
+    EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+    EXPECT_EQ(kSuspend, delegate_.GetActions());
+    EXPECT_FALSE(delegate_.suspend_wakeup_count_valid());
+    EXPECT_EQ(kSuspendSec, delegate_.suspend_duration().InSeconds());
+  }
+
+  // The next failure should result in the system shutting down.
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(JoinActions(kSuspend, kShutDown, NULL), delegate_.GetActions());
+}
+
+TEST_F(SuspenderTest, ReportInitialSuspendAttempts) {
+  Init();
+  suspender_.RequestSuspend();
+  EXPECT_EQ(kPrepare, delegate_.GetActions());
+
+  // Suspend successfully once and do a dark resume.
+  dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
+  dark_resume_.set_in_dark_resume(true);
+  dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(10));
+  suspender_.OnReadyForSuspend(test_api_.suspend_id());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+
+  // Report failure for the first attempt to resuspend from dark resume; then
+  // report success for the second attempt.
+  delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(kSuspend, delegate_.GetActions());
+  dark_resume_.set_in_dark_resume(false);
+  delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
+  EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
+  EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
+
+  // Check that the single initial suspend attempt is reported rather than the
+  // two attempts that occurred while in dark resume.
+  EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
+  EXPECT_TRUE(delegate_.suspend_was_successful());
+  EXPECT_EQ(1, delegate_.num_suspend_attempts());
+}
+
 }  // namespace policy
 }  // namespace power_manager