blob: 4038906609f982e24fccb1eaad93a4fe1af1653e [file] [log] [blame]
// Copyright 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "power_manager/powerd/system/dark_resume.h"
#include <stdint.h>
#include <algorithm>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <components/timers/alarm_timer_chromeos.h>
#include "power_manager/common/power_constants.h"
#include "power_manager/common/prefs.h"
#include "power_manager/common/util.h"
namespace power_manager {
namespace system {
namespace {
// Default file describing whether the system is currently in dark resume.
const char kDarkResumeStatePath[] = "/sys/power/dark_resume_state";
// In kernel 3.14 and later, we switch over to wakeup_type instead of
// dark_resume_state.
const char kWakeupTypePath[] = "/sys/power/wakeup_type";
} // namespace
const char DarkResume::kPowerDir[] = "power";
const char DarkResume::kActiveFile[] = "dark_resume_active";
const char DarkResume::kSourceFile[] = "dark_resume_source";
const char DarkResume::kEnabled[] = "enabled";
const char DarkResume::kDisabled[] = "disabled";
// For kernel 3.14 and later
const char DarkResume::kWakeupTypeFile[] = "wakeup_type";
const char DarkResume::kAutomatic[] = "automatic";
const char DarkResume::kUnknown[] = "unknown";
DarkResume::DarkResume()
: in_dark_resume_(false),
using_wakeup_type_(false),
power_supply_(NULL),
prefs_(NULL),
legacy_state_path_(kDarkResumeStatePath),
wakeup_state_path_(kWakeupTypePath),
battery_shutdown_threshold_(0.0),
next_action_(SUSPEND) {
}
DarkResume::~DarkResume() {
SetStates(dark_resume_sources_, using_wakeup_type_ ? kUnknown : kDisabled);
SetStates(dark_resume_devices_, kDisabled);
}
void DarkResume::Init(PowerSupplyInterface* power_supply,
PrefsInterface* prefs) {
power_supply_ = power_supply;
prefs_ = prefs;
scoped_ptr<timers::SimpleAlarmTimer> timer(new timers::SimpleAlarmTimer());
if (timer->can_wake_from_suspend())
timer_ = timer.Pass();
bool disable = false;
enabled_ = (!prefs_->GetBool(kDisableDarkResumePref, &disable) || !disable) &&
ReadSuspendDurationsPref();
LOG(INFO) << "Dark resume user space " << (enabled_ ? "enabled" : "disabled");
std::string source_file;
std::string source_state;
state_path_ = wakeup_state_path_;
if (base::PathExists(state_path_)) {
using_wakeup_type_ = true;
source_file = kWakeupTypeFile;
source_state = enabled_ ? kAutomatic : kUnknown;
} else if (base::PathExists(legacy_state_path_)) {
state_path_ = legacy_state_path_;
using_wakeup_type_ = false;
source_file = kSourceFile;
source_state = enabled_ ? kEnabled : kDisabled;
} else {
enabled_ = false;
LOG(WARNING) << "Dark resume state path not found";
}
GetFiles(&dark_resume_sources_, kDarkResumeSourcesPref, source_file);
GetFiles(&dark_resume_devices_, kDarkResumeDevicesPref, kActiveFile);
SetStates(dark_resume_sources_, source_state);
SetStates(dark_resume_devices_, (enabled_ ? kEnabled : kDisabled));
}
void DarkResume::PrepareForSuspendRequest() {
if (timer_ && enabled_)
ScheduleBatteryCheck();
}
void DarkResume::UndoPrepareForSuspendRequest() {
if (timer_)
timer_->Stop();
in_dark_resume_ = false;
}
void DarkResume::GetActionForSuspendAttempt(Action* action,
base::TimeDelta* suspend_duration) {
DCHECK(action);
DCHECK(suspend_duration);
if (!enabled_ || !power_supply_->RefreshImmediately()) {
*action = SUSPEND;
*suspend_duration = base::TimeDelta();
return;
}
if (timer_) {
*suspend_duration = base::TimeDelta();
} else {
UpdateNextAction();
*suspend_duration = GetNextSuspendDuration();
}
*action = next_action_;
}
void DarkResume::ScheduleBatteryCheck() {
if (!power_supply_->RefreshImmediately())
return;
UpdateNextAction();
timer_->Start(FROM_HERE,
GetNextSuspendDuration(),
base::Bind(&DarkResume::ScheduleBatteryCheck,
base::Unretained(this)));
}
base::TimeDelta DarkResume::GetNextSuspendDuration() {
const double battery = power_supply_->GetPowerStatus().battery_percentage;
SuspendMap::iterator suspend_it = suspend_durations_.upper_bound(battery);
if (suspend_it != suspend_durations_.begin())
suspend_it--;
return suspend_it->second;
}
void DarkResume::UpdateNextAction() {
const PowerStatus status = power_supply_->GetPowerStatus();
const double battery = status.battery_percentage;
const bool line_power = status.line_power_on;
const bool in_dark_resume = InDarkResume();
LOG(INFO) << (in_dark_resume ? "In" : "Not in") << " dark resume with "
<< "battery at " << battery << "% and line power "
<< (line_power ? "on" : "off");
// If suspending from the non-dark-resume state, or if the battery level has
// actually increased since the previous suspend attempt, update the shutdown
// threshold.
if (!in_dark_resume || battery > battery_shutdown_threshold_) {
battery_shutdown_threshold_ = battery;
LOG(INFO) << "Updated shutdown threshold to " << battery << "%";
}
next_action_ = (battery < battery_shutdown_threshold_ && !line_power)
? SHUT_DOWN : SUSPEND;
}
void DarkResume::HandleSuccessfulResume() {
if (!enabled_) {
in_dark_resume_ = false;
return;
}
std::string buf;
if (!base::ReadFileToString(state_path_, &buf)) {
PLOG(ERROR) << "Unable to read " << state_path_.value();
in_dark_resume_ = false;
return;
}
base::TrimWhitespaceASCII(buf, base::TRIM_TRAILING, &buf);
if (using_wakeup_type_) {
in_dark_resume_ = buf == kAutomatic;
} else {
uint64_t value = 0;
in_dark_resume_ = base::StringToUint64(buf, &value) && value;
}
}
bool DarkResume::InDarkResume() {
return in_dark_resume_;
}
bool DarkResume::IsEnabled() {
return enabled_;
}
bool DarkResume::ReadSuspendDurationsPref() {
suspend_durations_.clear();
std::string data;
if (!prefs_->GetString(kDarkResumeSuspendDurationsPref, &data))
return false;
base::StringPairs pairs;
base::TrimWhitespaceASCII(data, base::TRIM_TRAILING, &data);
if (!base::SplitStringIntoKeyValuePairs(data, ' ', '\n', &pairs)) {
LOG(ERROR) << "Unable to parse " << kDarkResumeSuspendDurationsPref;
return false;
}
for (size_t i = 0; i < pairs.size(); ++i) {
double battery_level = 0.0;
int suspend_duration = 0;
if (!base::StringToDouble(pairs[i].first, &battery_level) ||
!base::StringToInt(pairs[i].second, &suspend_duration)) {
LOG(ERROR) << "Unable to parse values on line " << i << " of "
<< kDarkResumeSuspendDurationsPref;
return false;
}
if (suspend_duration % base::TimeDelta::FromDays(1).InSeconds() == 0) {
LOG(ERROR) << "Suspend duration in " << kDarkResumeSuspendDurationsPref
<< " cannot be multiple of 86400";
return false;
}
suspend_durations_[battery_level] =
base::TimeDelta::FromSeconds(suspend_duration);
}
return !suspend_durations_.empty();
}
void DarkResume::GetFiles(std::vector<base::FilePath>* files,
const std::string& pref_name,
const std::string& base_file) {
files->clear();
std::string data;
if (!prefs_->GetString(pref_name, &data))
return;
std::vector<std::string> lines;
base::SplitString(data, '\n', &lines);
for (size_t i = 0; i < lines.size(); ++i) {
base::FilePath path = base::FilePath(lines[i]);
path = path.AppendASCII(kPowerDir);
path = path.AppendASCII(base_file.c_str());
files->push_back(path);
}
}
void DarkResume::SetStates(const std::vector<base::FilePath>& files,
const std::string& state) {
for (std::vector<base::FilePath>::const_iterator iter = files.begin();
iter != files.end(); ++iter) {
if (!util::WriteFileFully(*iter, state.c_str(), state.length())) {
PLOG(ERROR) << "Failed writing \"" << state << "\" to " << iter->value();
}
}
}
} // namespace system
} // namespace power_manager