chromium/chromeos/ash/components/disks/suspend_unmount_manager.cc

// Copyright 2015 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/disks/suspend_unmount_manager.h"

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "chromeos/ash/components/disks/disk.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"

namespace ash {
namespace disks {
namespace {

// Threshold for logging the blocking of suspend.
constexpr base::TimeDelta kBlockSuspendThreshold = base::Seconds(5);

void OnRefreshCompleted(bool success) {}

}  // namespace

SuspendUnmountManager::SuspendUnmountManager(
    DiskMountManager* disk_mount_manager)
    : disk_mount_manager_(disk_mount_manager) {
  chromeos::PowerManagerClient::Get()->AddObserver(this);
}

SuspendUnmountManager::~SuspendUnmountManager() {
  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
  if (block_suspend_token_)
    chromeos::PowerManagerClient::Get()->UnblockSuspend(block_suspend_token_);
}

void SuspendUnmountManager::SuspendImminent(
    power_manager::SuspendImminent::Reason reason) {
  DCHECK(unmounting_paths_.empty());
  if (!unmounting_paths_.empty())
    return;
  std::set<std::string> mount_paths;
  for (const auto& disk : disk_mount_manager_->disks()) {
    if ((disk->device_type() == DeviceType::kUSB ||
         disk->device_type() == DeviceType::kSD) &&
        !disk->mount_path().empty()) {
      mount_paths.insert(disk->mount_path());
    }
  }
  for (const auto& mount_path : mount_paths) {
    if (block_suspend_token_.is_empty()) {
      block_suspend_token_ = base::UnguessableToken::Create();
      block_suspend_time_ = base::TimeTicks::Now();
      chromeos::PowerManagerClient::Get()->BlockSuspend(
          block_suspend_token_, "SuspendUnmountManager");
    }
    disk_mount_manager_->UnmountPath(
        mount_path, base::BindOnce(&SuspendUnmountManager::OnUnmountComplete,
                                   weak_ptr_factory_.GetWeakPtr(), mount_path));
    unmounting_paths_.insert(mount_path);
  }
}

void SuspendUnmountManager::SuspendDone(base::TimeDelta sleep_duration) {
  // SuspendDone can be called before OnUnmountComplete when suspend is
  // cancelled, or it takes long time to unmount volumes.
  unmounting_paths_.clear();
  disk_mount_manager_->EnsureMountInfoRefreshed(
      base::BindOnce(&OnRefreshCompleted), true /* force */);
  block_suspend_token_ = {};
}

void SuspendUnmountManager::OnUnmountComplete(const std::string& mount_path,
                                              MountError error_code) {
  // This can happen when unmount completes after suspend done is called.
  if (unmounting_paths_.erase(mount_path) != 1)
    return;
  if (unmounting_paths_.empty() && block_suspend_token_) {
    chromeos::PowerManagerClient::Get()->UnblockSuspend(block_suspend_token_);
    block_suspend_token_ = {};

    auto block_time = base::TimeTicks::Now() - block_suspend_time_;
    LOG_IF(WARNING, block_time > kBlockSuspendThreshold)
        << "Blocked suspend for " << block_time.InSecondsF() << " seconds";
  }
}

}  // namespace disks
}  // namespace ash