chromium/chromeos/ash/components/power/dark_resume_controller.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/power/dark_resume_controller.h"

#include <utility>

#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"

namespace ash::system {

namespace {

// The default value of |dark_resume_hard_timeout_| till
// |PowerManagerInitialized| is called.
constexpr base::TimeDelta kDefaultDarkResumeHardTimeout = base::Seconds(20);

}  // namespace

// static.
constexpr base::TimeDelta DarkResumeController::kDarkResumeWakeLockCheckTimeout;

DarkResumeController::DarkResumeController(
    mojo::PendingRemote<device::mojom::WakeLockProvider> wake_lock_provider)
    : wake_lock_provider_(std::move(wake_lock_provider)),
      dark_resume_hard_timeout_(kDefaultDarkResumeHardTimeout) {
  DCHECK(!dark_resume_hard_timeout_.is_zero());
  chromeos::PowerManagerClient::Get()->AddObserver(this);
}

DarkResumeController::~DarkResumeController() {
  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
}

void DarkResumeController::PowerManagerInitialized() {
  dark_resume_hard_timeout_ =
      chromeos::PowerManagerClient::Get()->GetDarkSuspendDelayTimeout();
}

void DarkResumeController::DarkSuspendImminent() {
  DVLOG(1) << __func__;
  block_suspend_token_ = base::UnguessableToken::Create();
  chromeos::PowerManagerClient::Get()->BlockSuspend(block_suspend_token_,
                                                    "DarkResumeController");
  // Schedule task that will check for any wake locks acquired in dark resume.
  DCHECK(!wake_lock_check_timer_.IsRunning());
  wake_lock_check_timer_.Start(
      FROM_HERE, kDarkResumeWakeLockCheckTimeout,
      base::BindOnce(
          &DarkResumeController::HandleDarkResumeWakeLockCheckTimeout,
          weak_ptr_factory_.GetWeakPtr()));
}

void DarkResumeController::SuspendDone(base::TimeDelta sleep_duration) {
  DVLOG(1) << __func__;
  // Clear any dark resume state when the device resumes.
  ClearDarkResumeState();
}

void DarkResumeController::OnWakeLockDeactivated(
    device::mojom::WakeLockType type) {
  // If this callback fires then one of two scenarios happened -
  // 1. No wake lock was held after |kDarkResumeWakeLockCheckTimeout|.
  // 2. Wake lock was held but was released before the hard timeout.
  //
  // The device is now ready to re-suspend after this dark resume event. Tell
  // the power daemon to re-suspend and invalidate any other state associated
  // with dark resume.
  DVLOG(1) << __func__;
  // The observer is only registered once dark resume starts.
  DCHECK(block_suspend_token_);
  chromeos::PowerManagerClient::Get()->UnblockSuspend(block_suspend_token_);
  block_suspend_token_ = {};
  ClearDarkResumeState();
}

bool DarkResumeController::IsDarkResumeStateSetForTesting() const {
  return block_suspend_token_ && wake_lock_observer_receiver_.is_bound();
}

base::TimeDelta DarkResumeController::GetHardTimeoutForTesting() const {
  return dark_resume_hard_timeout_;
}

bool DarkResumeController::IsDarkResumeStateClearedForTesting() const {
  return !weak_ptr_factory_.HasWeakPtrs() &&
         !wake_lock_check_timer_.IsRunning() &&
         !hard_timeout_timer_.IsRunning() && !block_suspend_token_ &&
         !wake_lock_observer_receiver_.is_bound();
}

void DarkResumeController::HandleDarkResumeWakeLockCheckTimeout() {
  DVLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(dark_resume_tasks_sequence_checker_);
  wake_lock_check_timer_.Stop();
  // Setup observer for wake lock deactivation. If a wake lock is not activated
  // this calls back immediately, else whenever the wake lock is deactivated.
  // The device will be suspended on a deactivation notification i.e. in
  // OnDeactivation.
  wake_lock_provider_->NotifyOnWakeLockDeactivation(
      device::mojom::WakeLockType::kPreventAppSuspension,
      wake_lock_observer_receiver_.BindNewPipeAndPassRemote());

  // Schedule task that will tell the power daemon to re-suspend after a dark
  // resume irrespective of any state. This is a last resort timeout to ensure
  // the device doesn't stay up indefinitely in dark resume.
  DCHECK(!hard_timeout_timer_.IsRunning());
  hard_timeout_timer_.Start(
      FROM_HERE, dark_resume_hard_timeout_,
      base::BindOnce(&DarkResumeController::HandleDarkResumeHardTimeout,
                     weak_ptr_factory_.GetWeakPtr()));
}

void DarkResumeController::HandleDarkResumeHardTimeout() {
  DVLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(dark_resume_tasks_sequence_checker_);
  hard_timeout_timer_.Stop();
  // Enough is enough. Tell power daemon it's okay to suspend.
  DCHECK(block_suspend_token_);
  chromeos::PowerManagerClient::Get()->UnblockSuspend(block_suspend_token_);
  block_suspend_token_ = {};
  ClearDarkResumeState();
}

void DarkResumeController::ClearDarkResumeState() {
  DVLOG(1) << __func__;
  // Reset the token that is used to trigger a re-suspend. Won't be needed
  // if the dark resume state machine is ending.
  block_suspend_token_ = {};

  // This automatically invalidates any WakeLockObserver and associated callback
  // in this case OnDeactivation.
  wake_lock_observer_receiver_.reset();

  // Stops timer and invalidates HandleDarkResumeWakeLockCheckTimeout.
  wake_lock_check_timer_.Stop();

  // Stops timer and invalidates HandleDarkResumeHardTimeout.
  hard_timeout_timer_.Stop();

  // At this point all pending callbacks should be invalidated. This is a last
  // fail safe to not have any lingering tasks associated with the dark resume
  // state machine.
  weak_ptr_factory_.InvalidateWeakPtrs();
}

}  // namespace ash::system