blob: 0d7c6e0b42e9e1e463ea2509656cd1bd3b94b8af [file] [log] [blame]
// Copyright (c) 2012 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/power_supply.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <utility>
#include <base/bind.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#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 <base/strings/stringprintf.h>
#include <base/threading/thread_task_runner_handle.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/message.h>
#include "power_manager/common/battery_percentage_converter.h"
#include "power_manager/common/clock.h"
#include "power_manager/common/metrics_constants.h"
#include "power_manager/common/power_constants.h"
#include "power_manager/common/prefs.h"
#include "power_manager/common/util.h"
#include "power_manager/powerd/system/dbus_wrapper.h"
#include "power_manager/powerd/system/udev.h"
#include "power_manager/proto_bindings/power_supply_properties.pb.h"
namespace power_manager {
namespace system {
namespace {
// sysfs reports only integer values. For non-integral values, it scales them
// up by 10^6. This factor scales them back down accordingly.
const double kDoubleScaleFactor = 0.000001;
// Default time interval between polls, in milliseconds.
const int kDefaultPollMs = 30000;
// Default time interval between polls when the number of samples is less than
// |kMaxCurrentSamplesPref|, in milliseconds.
const int kDefaultPollInitialMs = 1000;
// Default values for |battery_stabilized_after_*_delay_|, in milliseconds.
const int kDefaultBatteryStabilizedAfterStartupDelayMs = 5000;
const int kDefaultBatteryStabilizedAfterLinePowerConnectedDelayMs = 5000;
const int kDefaultBatteryStabilizedAfterLinePowerDisconnectedDelayMs = 5000;
const int kDefaultBatteryStabilizedAfterResumeDelayMs = 5000;
// Reads the contents of |filename| within |directory| into |out|, trimming
// trailing whitespace. Returns true on success.
bool ReadAndTrimString(const base::FilePath& directory,
const std::string& filename,
std::string* out) {
return util::MaybeReadStringFile(directory.Append(filename), out);
}
// Reads a 64-bit integer value from a file and returns true on success.
bool ReadInt64(const base::FilePath& directory,
const std::string& filename,
int64_t* out) {
std::string buffer;
if (!ReadAndTrimString(directory, filename, &buffer))
return false;
return base::StringToInt64(buffer, out);
}
// Reads an integer value and scales it to a double (see |kDoubleScaleFactor|.
// Returns 0.0 on failure.
double ReadScaledDouble(const base::FilePath& directory,
const std::string& filename) {
int64_t value = 0;
return ReadInt64(directory, filename, &value) ? kDoubleScaleFactor * value
: 0.0;
}
// Returns the string surrounded by brackets via the |out| parameter.
// For example, returns "fun" given the string: "This format is not so [fun]"
// The return value is a boolean indicating true on success or false on failure.
bool ReadBracketSelectedString(const base::FilePath& directory,
const std::string& filename,
std::string* out) {
std::string buffer;
DCHECK(out);
if (!ReadAndTrimString(directory, filename, &buffer))
return false;
size_t start = buffer.find("[");
if (start == std::string::npos)
return false;
start++;
size_t end = buffer.find("]", start);
if (end == std::string::npos)
return false;
*out = buffer.substr(start, end - start);
return true;
}
// Returns true if |type|, a power supply type read from a "type" file in
// sysfs, indicates USB BC1.2 types.
bool IsLowPowerUsbChargerType(const std::string& type) {
return type == PowerSupply::kUsbType || type == PowerSupply::kUsbDcpType ||
type == PowerSupply::kUsbCdpType || type == PowerSupply::kUsbAcaType;
}
// Returns true if |type| ends with with the PD_DRP common suffix.
bool IsPdDrpType(const std::string& type) {
return base::EndsWith(type, PowerSupply::kUsbPdDrpType,
base::CompareCase::SENSITIVE);
}
// Returns true if |type|, a power supply type read from a "type" file in
// sysfs, indicates USB_PD_DRP, meaning a USB Power Delivery Dual Role Port.
bool IsDualRoleType(const std::string& type, const base::FilePath& path) {
// 4.19+ kernels have the type as just "USB", and an extra usb_type file
// in the form:
// Unknown SDP DCP CDP C PD [PD_DRP] BrickID
if (type == PowerSupply::kUsbType) {
std::string usb_type;
if (ReadBracketSelectedString(path, "usb_type", &usb_type))
return IsPdDrpType(usb_type);
}
return IsPdDrpType(type);
}
// Returns true if |path|, a sysfs directory, corresponds to an external
// peripheral (e.g. a wireless mouse or keyboard).
bool IsExternalPeripheral(const base::FilePath& path) {
std::string scope;
return ReadAndTrimString(path, "scope", &scope) && scope == "Device";
}
// Returns true if |path|, a sysfs directory, corresponds to a battery.
bool IsBatteryPresent(const base::FilePath& path) {
int64_t present = 0;
return ReadInt64(path, "present", &present) && present != 0;
}
// Returns a string describing |type|.
const char* ExternalPowerToString(PowerSupplyProperties::ExternalPower type) {
switch (type) {
case PowerSupplyProperties_ExternalPower_AC:
return "AC";
case PowerSupplyProperties_ExternalPower_USB:
return "USB";
case PowerSupplyProperties_ExternalPower_DISCONNECTED:
return "none";
}
return "unknown";
}
// Returns true if |port| is connected to a dedicated power source or dual-role
// device.
bool PortHasSourceOrDualRole(const PowerStatus::Port& port) {
return port.role == PowerStatus::Port::Role::DEDICATED_SOURCE ||
port.role == PowerStatus::Port::Role::DUAL_ROLE;
}
// Less-than comparator for PowerStatus::Port structs.
struct PortComparator {
bool operator()(const PowerStatus::Port& a, const PowerStatus::Port& b) {
return a.id < b.id;
}
};
// Maps names read from kChargingPortsPref to the corresponding
// PowerSupplyProperties::PowerSource::Port values.
PowerSupplyProperties::PowerSource::Port GetPortLocationFromString(
const std::string& name) {
if (name == "LEFT")
return PowerSupplyProperties_PowerSource_Port_LEFT;
else if (name == "RIGHT")
return PowerSupplyProperties_PowerSource_Port_RIGHT;
else if (name == "BACK")
return PowerSupplyProperties_PowerSource_Port_BACK;
else if (name == "FRONT")
return PowerSupplyProperties_PowerSource_Port_FRONT;
else if (name == "LEFT_FRONT")
return PowerSupplyProperties_PowerSource_Port_LEFT_FRONT;
else if (name == "LEFT_BACK")
return PowerSupplyProperties_PowerSource_Port_LEFT_BACK;
else if (name == "RIGHT_FRONT")
return PowerSupplyProperties_PowerSource_Port_RIGHT_FRONT;
else if (name == "RIGHT_BACK")
return PowerSupplyProperties_PowerSource_Port_RIGHT_BACK;
else if (name == "BACK_LEFT")
return PowerSupplyProperties_PowerSource_Port_BACK_LEFT;
else if (name == "BACK_RIGHT")
return PowerSupplyProperties_PowerSource_Port_BACK_RIGHT;
else
return PowerSupplyProperties_PowerSource_Port_UNKNOWN;
}
// Maps names read from power supply |type| sysfs nodes to the corresponding
// PowerSupplyProperties::PowerSource::Type values.
PowerSupplyProperties::PowerSource::Type GetPowerSourceTypeFromString(
const std::string& type) {
if (type == PowerSupply::kMainsType) {
return PowerSupplyProperties_PowerSource_Type_MAINS;
} else if (type == PowerSupply::kUsbCType ||
type == PowerSupply::kUsbPdType || IsPdDrpType(type) ||
type == PowerSupply::kBrickIdType) {
return PowerSupplyProperties_PowerSource_Type_USB_C;
} else if (type == PowerSupply::kUsbType ||
type == PowerSupply::kUsbAcaType ||
type == PowerSupply::kUsbCdpType ||
type == PowerSupply::kUsbDcpType) {
return PowerSupplyProperties_PowerSource_Type_USB_BC_1_2;
}
return PowerSupplyProperties_PowerSource_Type_OTHER;
}
} // namespace
void CopyPowerStatusToProtocolBuffer(const PowerStatus& status,
PowerSupplyProperties* proto) {
DCHECK(proto);
proto->Clear();
proto->set_external_power(status.external_power);
proto->set_battery_state(status.battery_state);
proto->set_supports_dual_role_devices(status.supports_dual_role_devices);
if (status.battery_state != PowerSupplyProperties_BatteryState_NOT_PRESENT) {
proto->set_battery_percent(status.display_battery_percentage);
// Show the user the time until powerd will shut down the system
// automatically rather than the time until the battery is completely empty.
proto->set_battery_time_to_empty_sec(
status.battery_time_to_shutdown.InSeconds());
proto->set_battery_time_to_full_sec(
status.battery_time_to_full.InSeconds());
proto->set_is_calculating_battery_time(status.is_calculating_battery_time);
if (status.battery_state == PowerSupplyProperties_BatteryState_FULL ||
status.battery_state == PowerSupplyProperties_BatteryState_CHARGING) {
proto->set_battery_discharge_rate(-status.battery_energy_rate);
} else {
proto->set_battery_discharge_rate(status.battery_energy_rate);
}
// Cros_healthd is interested in the following items for reporting
// telemetry data.
proto->set_battery_vendor(status.battery_vendor);
proto->set_battery_voltage(status.battery_voltage);
proto->set_battery_cycle_count(status.battery_cycle_count);
proto->set_battery_serial_number(status.battery_serial_number);
proto->set_battery_charge_full_design(status.battery_charge_full_design);
proto->set_battery_charge_full(status.battery_charge_full);
proto->set_battery_voltage_min_design(status.battery_voltage_min_design);
proto->set_battery_charge(status.battery_charge);
proto->set_battery_model_name(status.battery_model_name);
proto->set_battery_current(status.battery_current);
proto->set_battery_technology(status.battery_technology);
proto->set_battery_status(status.battery_status_string);
}
for (auto port : status.ports) {
// Chrome is only interested in ports that are currently in a state where
// they can deliver power.
if (!PortHasSourceOrDualRole(port))
continue;
PowerSupplyProperties::PowerSource* source =
proto->add_available_external_power_source();
source->set_id(port.id);
source->set_port(port.location);
source->set_type(GetPowerSourceTypeFromString(port.type));
source->set_manufacturer_id(port.manufacturer_id);
source->set_model_id(port.model_id);
source->set_max_power(port.max_power);
source->set_active_by_default(port.active_by_default);
}
if (!status.external_power_source_id.empty())
proto->set_external_power_source_id(status.external_power_source_id);
proto->set_preferred_minimum_external_power(
status.preferred_minimum_external_power);
}
std::string GetPowerStatusBatteryDebugString(const PowerStatus& status) {
if (!status.battery_is_present)
return std::string();
std::string output;
switch (status.external_power) {
case PowerSupplyProperties_ExternalPower_AC:
case PowerSupplyProperties_ExternalPower_USB: {
output = base::StringPrintf("On %s (%s",
ExternalPowerToString(status.external_power),
status.line_power_type.c_str());
// Add details in the form ", 1.253A at 14.7V, max 2.0A at 15.0V",
// omitting unavailable data.
std::string details;
if (status.has_line_power_current)
details = base::StringPrintf("%.3fA", status.line_power_current);
if (status.has_line_power_voltage) {
details += (details.empty() ? "" : " at ") +
base::StringPrintf("%.1fV", status.line_power_voltage);
}
if (status.line_power_max_current && status.line_power_max_voltage) {
details += (details.empty() ? "" : ", ") +
base::StringPrintf("max %.1fA at %.1fV",
status.line_power_max_current,
status.line_power_max_voltage);
}
if (!details.empty())
output += ", " + details;
output += ") with battery at ";
} break;
case PowerSupplyProperties_ExternalPower_DISCONNECTED:
output = "On battery at ";
break;
}
int rounded_actual = lround(status.battery_percentage);
int rounded_display = lround(status.display_battery_percentage);
output += base::StringPrintf("%d%%", rounded_actual);
if (rounded_actual != rounded_display)
output += base::StringPrintf(" (displayed as %d%%)", rounded_display);
output +=
base::StringPrintf(", %.3f/%.3fAh at %.3fA", status.battery_charge,
status.battery_charge_full, status.battery_current);
switch (status.battery_state) {
case PowerSupplyProperties_BatteryState_FULL:
output += ", full";
break;
case PowerSupplyProperties_BatteryState_CHARGING:
if (status.battery_time_to_full >= base::TimeDelta()) {
output += ", " + util::TimeDeltaToString(status.battery_time_to_full) +
" until full";
if (status.is_calculating_battery_time)
output += " (calculating)";
} else {
output += ", no estimate due to low averaged current";
}
break;
case PowerSupplyProperties_BatteryState_DISCHARGING:
if (status.battery_time_to_empty >= base::TimeDelta()) {
output += ", " + util::TimeDeltaToString(status.battery_time_to_empty) +
" until empty";
if (status.is_calculating_battery_time) {
output += " (calculating)";
} else if (status.battery_time_to_shutdown !=
status.battery_time_to_empty) {
output += base::StringPrintf(
" (%s until shutdown)",
util::TimeDeltaToString(status.battery_time_to_shutdown).c_str());
}
} else {
output += ", no estimate due to low averaged current";
}
break;
case PowerSupplyProperties_BatteryState_NOT_PRESENT:
break;
}
return output;
}
metrics::PowerSupplyType GetPowerSupplyTypeMetric(const std::string& type) {
if (type == PowerSupply::kMainsType)
return metrics::PowerSupplyType::MAINS;
else if (type == PowerSupply::kUsbType)
return metrics::PowerSupplyType::USB;
else if (type == PowerSupply::kUsbAcaType)
return metrics::PowerSupplyType::USB_ACA;
else if (type == PowerSupply::kUsbCdpType)
return metrics::PowerSupplyType::USB_CDP;
else if (type == PowerSupply::kUsbDcpType)
return metrics::PowerSupplyType::USB_DCP;
else if (type == PowerSupply::kUsbCType)
return metrics::PowerSupplyType::USB_C;
else if (type == PowerSupply::kUsbPdType)
return metrics::PowerSupplyType::USB_PD;
else if (IsPdDrpType(type))
return metrics::PowerSupplyType::USB_PD_DRP;
else if (type == PowerSupply::kBrickIdType)
return metrics::PowerSupplyType::BRICK_ID;
else
return metrics::PowerSupplyType::OTHER;
}
bool PowerStatus::Port::operator==(const Port& o) const {
return id == o.id && role == o.role && type == o.type &&
manufacturer_id == o.manufacturer_id && model_id == o.model_id &&
active_by_default == o.active_by_default;
}
// static
bool PowerSupply::ConnectedSourcesAreEqual(const PowerStatus& a,
const PowerStatus& b) {
auto a_it = a.ports.begin();
auto b_it = b.ports.begin();
while (true) {
// Walk each iterator forward to the next port with something connected that
// can supply power.
for (; a_it != a.ports.end() && !PortHasSourceOrDualRole(*a_it); ++a_it) {
}
for (; b_it != b.ports.end() && !PortHasSourceOrDualRole(*b_it); ++b_it) {
}
// If we reached the ends of both lists without finding any mismatches,
// report equality.
const bool a_done = a_it == a.ports.end();
const bool b_done = b_it == b.ports.end();
if (a_done && b_done)
return true;
// If we reached the end of one list but have a connected port in the other,
// or if the connected ports don't match, report inequality.
if (a_done != b_done || !(*a_it == *b_it))
return false;
a_it++;
b_it++;
}
NOTREACHED();
}
base::TimeTicks PowerSupply::TestApi::GetCurrentTime() const {
return power_supply_->clock_->GetCurrentTime();
}
void PowerSupply::TestApi::SetCurrentTime(base::TimeTicks now) {
power_supply_->clock_->set_current_time_for_testing(now);
}
void PowerSupply::TestApi::AdvanceTime(base::TimeDelta interval) {
power_supply_->clock_->set_current_time_for_testing(GetCurrentTime() +
interval);
}
bool PowerSupply::TestApi::TriggerPollTimeout() {
if (!power_supply_->poll_timer_.IsRunning())
return false;
power_supply_->poll_timer_.Stop();
power_supply_->OnPollTimeout();
return true;
}
const char PowerSupply::kUdevSubsystem[] = "power_supply";
const char PowerSupply::kChargeControlLimitMaxFile[] =
"charge_control_limit_max";
const char PowerSupply::kBatteryType[] = "Battery";
const char PowerSupply::kUnknownType[] = "Unknown";
const char PowerSupply::kMainsType[] = "Mains";
const char PowerSupply::kUsbType[] = "USB";
const char PowerSupply::kUsbAcaType[] = "USB_ACA";
const char PowerSupply::kUsbCdpType[] = "USB_CDP";
const char PowerSupply::kUsbDcpType[] = "USB_DCP";
const char PowerSupply::kUsbCType[] = "USB_C";
const char PowerSupply::kUsbPdType[] = "USB_PD";
// Cover both USB_PD_DRP in the "type" file of older kernels, as well as
// PD_DRP in the "usb_type" file of 4.19+ kernels.
const char PowerSupply::kUsbPdDrpType[] = "PD_DRP";
const char PowerSupply::kBrickIdType[] = "BrickID";
const char PowerSupply::kBatteryStatusCharging[] = "Charging";
const char PowerSupply::kBatteryStatusDischarging[] = "Discharging";
const char PowerSupply::kBatteryStatusNotCharging[] = "Not charging";
const char PowerSupply::kBatteryStatusFull[] = "Full";
const char PowerSupply::kLinePowerStatusCharging[] = "Charging";
const int PowerSupply::kObservedBatteryChargeRateMinMs = kDefaultPollMs;
const int PowerSupply::kBatteryStabilizedSlackMs = 50;
const double PowerSupply::kLowBatteryShutdownSafetyPercent = 5.0;
PowerSupply::PowerSupply()
: clock_(std::make_unique<Clock>()), weak_ptr_factory_(this) {}
PowerSupply::~PowerSupply() {
if (udev_)
udev_->RemoveSubsystemObserver(kUdevSubsystem, this);
}
void PowerSupply::Init(
const base::FilePath& power_supply_path,
PrefsInterface* prefs,
UdevInterface* udev,
system::DBusWrapperInterface* dbus_wrapper,
BatteryPercentageConverter* battery_percentage_converter) {
udev_ = udev;
udev_->AddSubsystemObserver(kUdevSubsystem, this);
prefs_ = prefs;
power_supply_path_ = power_supply_path;
dbus_wrapper_ = dbus_wrapper;
dbus_wrapper->ExportMethod(
kGetPowerSupplyPropertiesMethod,
base::Bind(&PowerSupply::OnGetPowerSupplyPropertiesMethodCall,
weak_ptr_factory_.GetWeakPtr()));
dbus_wrapper->ExportMethod(
kSetPowerSourceMethod,
base::Bind(&PowerSupply::OnSetPowerSourceMethodCall,
weak_ptr_factory_.GetWeakPtr()));
battery_percentage_converter_ = battery_percentage_converter;
prefs_->GetBool(kMultipleBatteriesPref, &allow_multiple_batteries_);
poll_delay_ = GetMsPref(kBatteryPollIntervalPref, kDefaultPollMs);
poll_delay_initial_ =
GetMsPref(kBatteryPollIntervalInitialPref, kDefaultPollInitialMs);
battery_stabilized_after_startup_delay_ =
GetMsPref(kBatteryStabilizedAfterStartupMsPref,
kDefaultBatteryStabilizedAfterStartupDelayMs);
battery_stabilized_after_line_power_connected_delay_ =
GetMsPref(kBatteryStabilizedAfterLinePowerConnectedMsPref,
kDefaultBatteryStabilizedAfterLinePowerConnectedDelayMs);
battery_stabilized_after_line_power_disconnected_delay_ =
GetMsPref(kBatteryStabilizedAfterLinePowerDisconnectedMsPref,
kDefaultBatteryStabilizedAfterLinePowerDisconnectedDelayMs);
battery_stabilized_after_resume_delay_ =
GetMsPref(kBatteryStabilizedAfterResumeMsPref,
kDefaultBatteryStabilizedAfterResumeDelayMs);
prefs_->GetDouble(kPowerSupplyFullFactorPref, &full_factor_);
full_factor_ = std::min(std::max(kEpsilon, full_factor_), 1.0);
LOG(INFO) << "Using full factor of " << full_factor_;
prefs_->GetDouble(kUsbMinAcWattsPref, &usb_min_ac_watts_);
int64_t shutdown_time_sec = 0;
if (prefs_->GetInt64(kLowBatteryShutdownTimePref, &shutdown_time_sec)) {
low_battery_shutdown_time_ =
base::TimeDelta::FromSeconds(shutdown_time_sec);
}
// The percentage-based threshold takes precedence over the time-based
// threshold. This behavior is duplicated in check_powerd_config.
if (prefs_->GetDouble(kLowBatteryShutdownPercentPref,
&low_battery_shutdown_percent_)) {
low_battery_shutdown_time_ = base::TimeDelta();
}
int64_t samples = 0;
CHECK(prefs_->GetInt64(kMaxCurrentSamplesPref, &samples));
current_samples_on_line_power_.reset(new RollingAverage(samples));
current_samples_on_battery_power_.reset(new RollingAverage(samples));
CHECK(prefs_->GetInt64(kMaxChargeSamplesPref, &samples));
charge_samples_.reset(new RollingAverage(samples));
LOG(INFO) << "Using low battery time threshold of "
<< low_battery_shutdown_time_.InSeconds()
<< " secs and using low battery percent threshold of "
<< low_battery_shutdown_percent_;
std::string ports_string;
if (prefs_->GetString(kChargingPortsPref, &ports_string)) {
base::TrimWhitespaceASCII(ports_string, base::TRIM_TRAILING, &ports_string);
base::StringPairs pairs;
if (!base::SplitStringIntoKeyValuePairs(ports_string, ' ', '\n', &pairs))
LOG(FATAL) << "Failed parsing " << kChargingPortsPref << " pref";
for (const auto& pair : pairs) {
const PowerSupplyProperties::PowerSource::Port location =
GetPortLocationFromString(pair.second);
if (location == PowerSupplyProperties_PowerSource_Port_UNKNOWN) {
LOG(FATAL) << "Unrecognized port \"" << pair.second << "\" for \""
<< pair.first << "\" in " << kChargingPortsPref << " pref";
}
if (!port_names_.insert(std::make_pair(pair.first, location)).second) {
LOG(FATAL) << "Duplicate entry for \"" << pair.first << "\" in "
<< kChargingPortsPref << " pref";
}
}
}
DeferBatterySampling(battery_stabilized_after_startup_delay_);
SchedulePoll();
}
void PowerSupply::AddObserver(PowerSupplyObserver* observer) {
DCHECK(observer);
observers_.AddObserver(observer);
}
void PowerSupply::RemoveObserver(PowerSupplyObserver* observer) {
DCHECK(observer);
observers_.RemoveObserver(observer);
}
PowerStatus PowerSupply::GetPowerStatus() const {
return power_status_;
}
bool PowerSupply::RefreshImmediately() {
return PerformUpdate(UpdatePolicy::UNCONDITIONALLY,
NotifyPolicy::ASYNCHRONOUSLY);
}
void PowerSupply::SetSuspended(bool suspended) {
if (is_suspended_ == suspended)
return;
is_suspended_ = suspended;
if (is_suspended_) {
VLOG(1) << "Stopping polling due to suspend";
poll_timer_.Stop();
current_poll_delay_for_testing_ = base::TimeDelta();
} else {
DeferBatterySampling(battery_stabilized_after_resume_delay_);
charge_samples_->Clear();
current_samples_on_line_power_->Clear();
has_max_samples_ = false;
PerformUpdate(UpdatePolicy::UNCONDITIONALLY, NotifyPolicy::ASYNCHRONOUSLY);
}
}
void PowerSupply::OnUdevEvent(const UdevEvent& event) {
VLOG(1) << "Got udev event for " << event.device_info.sysname;
// Bail out of the update if the available power sources didn't actually
// change to avoid recording new samples and updating battery estimates in
// response to spurious udev events (see http://crosbug.com/p/37403).
if (!is_suspended_) {
PerformUpdate(UpdatePolicy::ONLY_IF_STATE_CHANGED,
NotifyPolicy::SYNCHRONOUSLY);
}
}
std::string PowerSupply::GetIdForPath(const base::FilePath& path) const {
DCHECK(power_supply_path_.IsParent(path))
<< path.value() << " isn't a child of " << power_supply_path_.value();
return path.BaseName().value();
}
base::FilePath PowerSupply::GetPathForId(const std::string& id) const {
// Double-check that nobody's playing games with bogus IDs.
if (id.empty() || id == "." || id == ".." ||
id.find('/') != std::string::npos) {
LOG(WARNING) << "Got invalid ID \"" << id << "\"";
return base::FilePath();
}
base::FilePath path = power_supply_path_.Append(id);
if (!base::DirectoryExists(path)) {
LOG(WARNING) << "Got invalid ID \"" << id << "\"";
return base::FilePath();
}
return path;
}
base::TimeDelta PowerSupply::GetMsPref(const std::string& pref_name,
int64_t default_duration_ms) const {
prefs_->GetInt64(pref_name, &default_duration_ms);
return base::TimeDelta::FromMilliseconds(default_duration_ms);
}
void PowerSupply::DeferBatterySampling(base::TimeDelta stabilized_delay) {
const base::TimeTicks now = clock_->GetCurrentTime();
battery_stabilized_timestamp_ =
std::max(battery_stabilized_timestamp_, now + stabilized_delay);
VLOG(1) << "Waiting "
<< (battery_stabilized_timestamp_ - now).InMilliseconds()
<< " ms for battery current and charge to stabilize";
}
bool PowerSupply::UpdatePowerStatus(UpdatePolicy policy) {
CHECK(prefs_) << "PowerSupply::Init() wasn't called";
VLOG(1) << "Updating power status";
PowerStatus status;
// Track whether we found at least one (possibly offline) power source.
bool saw_power_source = false;
std::vector<base::FilePath> battery_paths;
// Iterate through sysfs's power supply information.
base::FileEnumerator file_enum(power_supply_path_, false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath path = file_enum.Next(); !path.empty();
path = file_enum.Next()) {
if (IsExternalPeripheral(path))
continue;
std::string type;
if (!base::ReadFileToString(path.Append("type"), &type))
continue;
base::TrimWhitespaceASCII(type, base::TRIM_TRAILING, &type);
saw_power_source = true;
// The battery state is dependent on the line power state, so defer reading
// it until all other directories have been examined.
if (type == kBatteryType)
battery_paths.push_back(path);
else
ReadLinePowerDirectory(path, &status);
}
// If no battery was found, assume that the system is actually on AC power.
if (!status.line_power_on &&
(battery_paths.empty() || !IsBatteryPresent(battery_paths[0]))) {
if (saw_power_source) {
// Batteryless Chromeboxes sometimes don't report any power sources. If we
// saw at least one source but it wasn't online, the battery status might
// be getting misreported, though; log a warning.
LOG(WARNING) << "Found neither line power nor a battery; assuming that "
<< "line power is connected";
}
status.line_power_on = true;
status.line_power_type = kMainsType;
status.external_power = PowerSupplyProperties_ExternalPower_AC;
}
// Sort the port list as needed by ConnectedSourcesAreEqual().
std::sort(status.ports.begin(), status.ports.end(), PortComparator());
status.preferred_minimum_external_power = usb_min_ac_watts_;
// Even though we haven't successfully finished initializing the status yet,
// save what we have so far so that if we bail out early due to a messed-up
// battery we'll at least start out knowing whether line power is connected.
if (!power_status_initialized_)
power_status_ = status;
// Finally, read the battery status.
std::sort(battery_paths.begin(), battery_paths.end());
if (!allow_multiple_batteries_ && battery_paths.size() > 1) {
for (size_t i = 1; i < battery_paths.size(); i++)
LOG(WARNING) << "Ignoring extra battery " << battery_paths[i].value();
battery_paths.resize(1);
}
if (battery_paths.size() == 1) {
if (!ReadBatteryDirectory(battery_paths[0], &status,
false /* allow_empty */))
return false;
} else if (battery_paths.size() > 1) {
if (!ReadMultipleBatteryDirectories(battery_paths, &status))
return false;
}
// Bail out before recording charge and current samples if this was a spurious
// update request. A change in |battery_charge_full| is used as a proxy for a
// battery being added or removed.
if (policy == UpdatePolicy::ONLY_IF_STATE_CHANGED &&
power_status_initialized_ &&
status.external_power == power_status_.external_power &&
status.battery_state == power_status_.battery_state &&
status.battery_percentage == power_status_.battery_percentage &&
ConnectedSourcesAreEqual(status, power_status_) &&
status.battery_charge_full == power_status_.battery_charge_full)
return false;
// Update running averages and use them to compute battery estimates.
if (status.battery_is_present) {
if (power_status_initialized_ &&
status.line_power_on != power_status_.line_power_on) {
DeferBatterySampling(
status.line_power_on
? battery_stabilized_after_line_power_connected_delay_
: battery_stabilized_after_line_power_disconnected_delay_);
charge_samples_->Clear();
has_max_samples_ = false;
// Chargers can deliver highly-variable currents depending on various
// factors (e.g. negotiated current for USB chargers, charge level, etc.).
// If one was just connected, throw away the previous average.
if (status.line_power_on)
current_samples_on_line_power_->Clear();
}
base::TimeTicks now = clock_->GetCurrentTime();
if (now >= battery_stabilized_timestamp_) {
charge_samples_->AddSample(status.battery_charge, now);
if (status.battery_current > 0.0) {
const double signed_current =
(status.battery_state ==
PowerSupplyProperties_BatteryState_DISCHARGING)
? -status.battery_current
: status.battery_current;
const auto& current_samples = status.line_power_on
? current_samples_on_line_power_
: current_samples_on_battery_power_;
current_samples->AddSample(signed_current, now);
if (!has_max_samples_)
has_max_samples_ = current_samples->HasMaxSamples();
num_zero_samples_ = 0;
} else {
num_zero_samples_++;
}
}
UpdateObservedBatteryChargeRate(&status);
status.is_calculating_battery_time = !UpdateBatteryTimeEstimates(&status);
status.battery_below_shutdown_threshold =
IsBatteryBelowShutdownThreshold(status);
}
power_status_ = status;
power_status_initialized_ = true;
return true;
}
void PowerSupply::ReadLinePowerDirectory(const base::FilePath& path,
PowerStatus* status) {
// Add the port and fill in its details as we go.
status->ports.push_back(PowerStatus::Port());
PowerStatus::Port* port = &status->ports.back();
port->id = GetIdForPath(path);
const auto location_it = port_names_.find(path.BaseName().value());
if (location_it != port_names_.end())
port->location = location_it->second;
// Bidirectional/dual-role ports export a "status" field.
std::string line_status;
ReadAndTrimString(path, "status", &line_status);
const bool dual_role_port = !line_status.empty();
if (dual_role_port)
status->supports_dual_role_devices = true;
// An "Unknown" type indicates a sink-only device that can't supply power.
ReadAndTrimString(path, "type", &port->type);
if (port->type == kUnknownType)
return;
const bool dual_role_connected = IsDualRoleType(port->type, path);
// If "online" is 0, nothing is connected unless it is USB_PD_DRP, in which
// case a value of 0 indicates we're connected to a dual-role device but not
// sinking power.
int64_t online = 0;
if ((!ReadInt64(path, "online", &online) || !online) && !dual_role_connected)
return;
// If we've made it this far, there's a dedicated source or dual-role device
// connected.
port->role = dual_role_connected ? PowerStatus::Port::Role::DUAL_ROLE
: PowerStatus::Port::Role::DEDICATED_SOURCE;
// Chargers connected to non-dual-role Chromebook systems are always active by
// default. The USB PD kernel driver will report "USB_PD_DRP" for dual-role
// devices (which aren't active by default). See http://crbug.com/459412 for
// additional discussion.
port->active_by_default = !dual_role_port || !dual_role_connected;
ReadAndTrimString(path, "manufacturer", &port->manufacturer_id);
ReadAndTrimString(path, "model_name", &port->model_id);
const double max_voltage = ReadScaledDouble(path, "voltage_max_design");
const double max_current = ReadScaledDouble(path, "current_max");
port->max_power = max_voltage * max_current; // watts
VLOG(1) << "Added power source " << port->id << ":"
<< " location=" << port->location
<< " manufacturer=" << port->manufacturer_id
<< " model=" << port->model_id << " max_power=" << port->max_power
<< " active_by_default=" << port->active_by_default;
// If this is a dual-role device, make sure that we're actually getting
// charged by it.
if (dual_role_port && line_status != kLinePowerStatusCharging)
return;
// We don't support (or expect) multiple online line power sources, but an
// extra "Mains" source can be reported if a system supports both Type-C and
// barrel jack charging and is using the ACPI driver. Favor the non-Mains
// source in this case.
if (!status->line_power_path.empty()) {
if (port->type == PowerSupply::kMainsType)
return;
if (status->line_power_type != PowerSupply::kMainsType) {
LOG(WARNING) << "Skipping additional line power source at "
<< path.value() << " (previously saw "
<< status->line_power_path << ")";
return;
}
// If we get here, then we're replacing an already-seen Mains source with
// this new non-Mains source.
}
status->line_power_on = true;
status->line_power_path = path.value();
status->line_power_type = port->type;
status->line_power_max_voltage = max_voltage;
status->line_power_max_current = max_current;
if (base::PathExists(path.Append("voltage_now"))) {
status->line_power_voltage = ReadScaledDouble(path, "voltage_now");
status->has_line_power_voltage = true;
}
if (base::PathExists(path.Append("current_now"))) {
status->line_power_current = ReadScaledDouble(path, "current_now");
status->has_line_power_current = true;
}
if (base::PathExists(path.Append("voltage_max_design"))) {
status->line_power_max_voltage =
ReadScaledDouble(path, "voltage_max_design");
status->has_line_power_max_voltage = true;
}
if (base::PathExists(path.Append("current_max"))) {
status->line_power_max_current = ReadScaledDouble(path, "current_max");
status->has_line_power_max_current = true;
}
// The USB PD driver reports the maximum power as being 0 watts while it's
// being determined; avoid reporting a low-power charger in that case.
const bool max_power_is_less_than_ac_min =
port->max_power > 0.0 && port->max_power < usb_min_ac_watts_;
if (!dual_role_port && IsLowPowerUsbChargerType(port->type)) {
// On spring, report all non-official chargers (which are reported as type
// USB* rather than Mains) as being low-power.
status->external_power = PowerSupplyProperties_ExternalPower_USB;
} else if (dual_role_port && max_power_is_less_than_ac_min) {
// For dual-role USB PD devices, check whether the maximum supported power
// is below the configured threshold.
status->external_power = PowerSupplyProperties_ExternalPower_USB;
} else {
// Otherwise, report a high-power source.
status->external_power = PowerSupplyProperties_ExternalPower_AC;
}
status->external_power_source_id = port->id;
VLOG(1) << "Found line power of type \"" << status->line_power_type
<< "\" at " << path.value();
}
bool PowerSupply::ReadBatteryDirectory(const base::FilePath& path,
PowerStatus* status,
bool allow_empty) {
VLOG(1) << "Reading battery status from " << path.value();
status->battery_path = path.value();
status->battery_is_present = IsBatteryPresent(path);
if (!status->battery_is_present)
return true;
ReadAndTrimString(path, "status", &status->battery_status_string);
// POWER_SUPPLY_PROP_VENDOR does not seem to be a valid property
// defined in <linux/power_supply.h>.
ReadAndTrimString(
path,
base::PathExists(path.Append("manufacturer")) ? "manufacturer" : "vendor",
&status->battery_vendor);
ReadAndTrimString(path, "model_name", &status->battery_model_name);
ReadAndTrimString(path, "technology", &status->battery_technology);
double voltage = ReadScaledDouble(path, "voltage_now");
status->battery_voltage = voltage;
int64_t cycle_count = 0;
if (ReadInt64(path, "cycle_count", &cycle_count)) {
status->battery_cycle_count = cycle_count;
}
ReadAndTrimString(path, "serial_number", &status->battery_serial_number);
// Attempt to determine nominal voltage for time-remaining calculations. This
// may or may not be the same as the instantaneous voltage |battery_voltage|,
// as voltage levels vary over the time the battery is charged or discharged.
// Some batteries don't have a voltage_min/max_design attribute, so just use
// the current voltage in that case.
double nominal_voltage = voltage;
// TODO(khegde): https://crbug.com/980246
if (base::PathExists(path.Append("voltage_min_design"))) {
status->battery_voltage_min_design =
ReadScaledDouble(path, "voltage_min_design");
nominal_voltage = status->battery_voltage_min_design;
} else if (base::PathExists(path.Append("voltage_max_design"))) {
nominal_voltage = ReadScaledDouble(path, "voltage_max_design");
}
// Nominal voltage is not required to obtain the charge level; if it's
// missing, just use |battery_voltage|.
if (nominal_voltage <= 0) {
if (voltage <= 0) {
// Avoid passing bad time-to-empty estimates to Chrome:
// http://crbug.com/671374
LOG(WARNING) << "Ignoring reading with bad or missing nominal ("
<< nominal_voltage << ") and instantaneous (" << voltage
<< ") voltages";
return false;
} else {
LOG(WARNING) << "Got nominal voltage " << nominal_voltage << "; using "
<< "instantaneous voltage " << voltage << " instead";
nominal_voltage = voltage;
}
}
status->nominal_voltage = nominal_voltage;
// ACPI has two different battery types: charge_battery and energy_battery.
// The main difference is that charge_battery type exposes
// 1. current_now in A
// 2. charge_{now, full, full_design} in Ah
// while energy_battery type exposes
// 1. power_now W
// 2. energy_{now, full, full_design} in Wh
// Change all the energy readings to charge format.
// If both energy and charge reading are present (some non-ACPI drivers
// expose both readings), read only the charge format.
//
// Some other batteries have more than that. If it has the charge attributes,
// just read those and the energy_now attribute.
double charge_full = 0;
double charge_full_design = 0;
double charge = 0;
double energy = 0;
if (base::PathExists(path.Append("energy_now")))
energy = ReadScaledDouble(path, "energy_now");
if (base::PathExists(path.Append("charge_full"))) {
charge_full = ReadScaledDouble(path, "charge_full");
charge_full_design = ReadScaledDouble(path, "charge_full_design");
charge = ReadScaledDouble(path, "charge_now");
if (energy <= 0.0)
energy = charge * nominal_voltage;
} else if (base::PathExists(path.Append("energy_full"))) {
DCHECK_GT(nominal_voltage, 0);
charge_full = ReadScaledDouble(path, "energy_full") / nominal_voltage;
charge_full_design =
ReadScaledDouble(path, "energy_full_design") / nominal_voltage;
charge = energy / nominal_voltage;
} else {
LOG(WARNING) << "Ignoring reading without battery charge/energy";
return false;
}
// Drop bogus readings (sometimes seen during firmware updates) that can
// confuse users: https://crbug.com/924869
if (charge_full <= 0.0 || charge < 0.0 || (charge == 0.0 && !allow_empty)) {
LOG(WARNING) << "Ignoring reading with battery charge " << charge
<< " and battery-full charge " << charge_full;
return false;
}
status->battery_charge_full = charge_full;
status->battery_charge_full_design = charge_full_design;
status->battery_charge = charge;
status->battery_energy = energy;
// The current can be reported as negative on some systems but not on others,
// so it can't be used to determine whether the battery is charging or
// discharging.
double current = base::PathExists(path.Append("power_now"))
? fabs(ReadScaledDouble(path, "power_now")) / voltage
: fabs(ReadScaledDouble(path, "current_now"));
status->battery_current = current;
status->battery_energy_rate = current * voltage;
UpdateBatteryPercentagesAndState(status);
return true;
}
void PowerSupply::UpdateBatteryPercentagesAndState(PowerStatus* status) {
DCHECK(status);
status->battery_percentage = util::ClampPercent(
100.0 * status->battery_charge / status->battery_charge_full);
status->display_battery_percentage =
battery_percentage_converter_->ConvertActualToDisplay(
status->battery_percentage);
if (status->line_power_on) {
const bool is_full =
status->battery_charge >= status->battery_charge_full * full_factor_;
if (is_full) {
status->battery_state = PowerSupplyProperties_BatteryState_FULL;
} else if (status->battery_current > 0.0 &&
(status->battery_status_string == kBatteryStatusCharging ||
status->battery_status_string == kBatteryStatusFull)) {
status->battery_state = PowerSupplyProperties_BatteryState_CHARGING;
} else {
status->battery_state = PowerSupplyProperties_BatteryState_DISCHARGING;
}
} else {
status->battery_state = PowerSupplyProperties_BatteryState_DISCHARGING;
}
}
bool PowerSupply::ReadMultipleBatteryDirectories(
const std::vector<base::FilePath>& paths, PowerStatus* status) {
DCHECK_GE(paths.size(), 2);
std::vector<PowerStatus> battery_statuses;
for (const auto& path : paths) {
PowerStatus battery_status(*status);
if (ReadBatteryDirectory(path, &battery_status, true /* allow_empty */))
battery_statuses.push_back(battery_status);
else
LOG(WARNING) << "Ignoring battery at " << path.value();
}
if (battery_statuses.empty()) {
LOG(WARNING) << "No functional batteries found";
return false;
}
// Sum data across all directories.
*status = battery_statuses[0];
for (size_t i = 1; i < battery_statuses.size(); ++i) {
const PowerStatus& s = battery_statuses[i];
status->battery_energy += s.battery_energy;
status->battery_energy_rate += s.battery_energy_rate;
status->battery_voltage += s.battery_voltage;
status->battery_current += s.battery_current;
status->battery_charge += s.battery_charge;
status->battery_charge_full += s.battery_charge_full;
status->battery_charge_full_design += s.battery_charge_full_design;
status->nominal_voltage += s.nominal_voltage;
if (s.battery_is_present)
status->battery_is_present = true;
// If any battery is charging or full, use charging as the combined status.
// Note that UpdateBatteryPercentagesAndState may still choose to report the
// battery as full (if the combined charge is high enough) or even
// discharging (if the current is zero or negative, or line power is
// disconnected and one battery is just charging from another).
if (s.battery_status_string == kBatteryStatusCharging ||
s.battery_status_string == kBatteryStatusFull)
status->battery_status_string = kBatteryStatusCharging;
}
// If all batteries reported being empty, something is likely wrong:
// https://crbug.com/924869
if (status->battery_charge == 0.0) {
LOG(WARNING) << "Ignoring zero summed battery charge";
return false;
}
// Compute percentages and state based on the combined values.
UpdateBatteryPercentagesAndState(status);
return true;
}
bool PowerSupply::UpdateBatteryTimeEstimates(PowerStatus* status) {
DCHECK(status);
status->battery_time_to_full = base::TimeDelta();
status->battery_time_to_empty = base::TimeDelta();
status->battery_time_to_shutdown = base::TimeDelta();
if (!has_max_samples_)
return false;
if (clock_->GetCurrentTime() < battery_stabilized_timestamp_)
return false;
// Positive if the battery is charging and negative if it's discharging.
const double signed_current =
status->line_power_on ? current_samples_on_line_power_->GetAverage()
: current_samples_on_battery_power_->GetAverage();
switch (status->battery_state) {
case PowerSupplyProperties_BatteryState_CHARGING:
if (signed_current <= kEpsilon) {
status->battery_time_to_full = base::TimeDelta::FromSeconds(-1);
} else {
const double charge_to_full =
std::max(0.0, status->battery_charge_full * full_factor_ -
status->battery_charge);
status->battery_time_to_full = base::TimeDelta::FromSeconds(
roundl(3600 * charge_to_full / signed_current));
}
break;
case PowerSupplyProperties_BatteryState_DISCHARGING:
if (signed_current >= -kEpsilon) {
status->battery_time_to_empty = base::TimeDelta::FromSeconds(-1);
status->battery_time_to_shutdown = base::TimeDelta::FromSeconds(-1);
} else {
status->battery_time_to_empty = base::TimeDelta::FromSeconds(
roundl(3600 * (status->battery_charge * status->nominal_voltage) /
(-signed_current * status->battery_voltage)));
const double shutdown_charge =
status->battery_charge_full * low_battery_shutdown_percent_ / 100.0;
const double available_charge =
std::max(0.0, status->battery_charge - shutdown_charge);
status->battery_time_to_shutdown =
base::TimeDelta::FromSeconds(
roundl(3600 * (available_charge * status->nominal_voltage) /
(-signed_current * status->battery_voltage))) -
low_battery_shutdown_time_;
status->battery_time_to_shutdown =
std::max(base::TimeDelta(), status->battery_time_to_shutdown);
}
break;
case PowerSupplyProperties_BatteryState_FULL:
break;
default:
NOTREACHED() << "Unhandled battery state "
<< static_cast<int>(status->battery_state);
}
return true;
}
void PowerSupply::UpdateObservedBatteryChargeRate(PowerStatus* status) const {
DCHECK(status);
const base::TimeDelta time_delta = charge_samples_->GetTimeDelta();
status->observed_battery_charge_rate =
(time_delta.InMilliseconds() < kObservedBatteryChargeRateMinMs)
? 0.0
: charge_samples_->GetValueDelta() / (time_delta.InSecondsF() / 3600);
}
bool PowerSupply::IsBatteryBelowShutdownThreshold(
const PowerStatus& status) const {
if (low_battery_shutdown_time_ == base::TimeDelta() &&
low_battery_shutdown_percent_ <= kEpsilon)
return false;
const bool below_threshold =
(status.battery_time_to_empty > base::TimeDelta() &&
status.battery_time_to_empty <= low_battery_shutdown_time_ &&
status.battery_percentage <= kLowBatteryShutdownSafetyPercent) ||
status.battery_percentage <= low_battery_shutdown_percent_;
// Most AC chargers can deliver enough current to prevent the battery from
// discharging while the device is in use; other chargers (e.g. USB) may not
// be able to, though. The observed charge rate is checked to verify whether
// the battery's charge is increasing or decreasing.
if (status.line_power_on)
return below_threshold && status.observed_battery_charge_rate < 0.0;
return below_threshold;
}
bool PowerSupply::PerformUpdate(UpdatePolicy update_policy,
NotifyPolicy notify_policy) {
const bool success = UpdatePowerStatus(update_policy);
if (!is_suspended_)
SchedulePoll();
if (!success)
return false;
if (notify_policy == NotifyPolicy::SYNCHRONOUSLY) {
NotifyObservers();
} else {
notify_observers_task_.Reset(
base::Bind(&PowerSupply::NotifyObservers, base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, notify_observers_task_.callback());
}
PowerSupplyProperties protobuf;
CopyPowerStatusToProtocolBuffer(power_status_, &protobuf);
dbus_wrapper_->EmitSignalWithProtocolBuffer(kPowerSupplyPollSignal, protobuf);
return true;
}
void PowerSupply::SchedulePoll() {
base::TimeDelta delay;
base::TimeTicks now = clock_->GetCurrentTime();
int64_t samples = 0;
CHECK(prefs_->GetInt64(kMaxCurrentSamplesPref, &samples));
// Wait |kBatteryStabilizedSlackMs| after |battery_stabilized_timestamp_| to
// start polling for the current and charge to stabilized.
// Poll every |poll_delay_initial_| ms until having |kMaxCurrentSamplesPref|
// samples then poll every |poll_delay_|.
if (battery_stabilized_timestamp_ > now) {
delay = battery_stabilized_timestamp_ - now +
base::TimeDelta::FromMilliseconds(kBatteryStabilizedSlackMs);
} else if (!has_max_samples_ && num_zero_samples_ < samples) {
delay = poll_delay_initial_;
} else {
delay = poll_delay_;
}
VLOG(1) << "Scheduling update in " << delay.InMilliseconds() << " ms";
poll_timer_.Start(FROM_HERE, delay, this, &PowerSupply::OnPollTimeout);
current_poll_delay_for_testing_ = delay;
}
void PowerSupply::OnPollTimeout() {
current_poll_delay_for_testing_ = base::TimeDelta();
PerformUpdate(UpdatePolicy::UNCONDITIONALLY, NotifyPolicy::SYNCHRONOUSLY);
}
void PowerSupply::NotifyObservers() {
for (PowerSupplyObserver& observer : observers_)
observer.OnPowerStatusUpdate();
}
void PowerSupply::OnGetPowerSupplyPropertiesMethodCall(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
PowerSupplyProperties protobuf;
CopyPowerStatusToProtocolBuffer(power_status_, &protobuf);
std::unique_ptr<dbus::Response> response =
dbus::Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
writer.AppendProtoAsArrayOfBytes(protobuf);
std::move(response_sender).Run(std::move(response));
}
void PowerSupply::OnSetPowerSourceMethodCall(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
std::string id;
dbus::MessageReader reader(method_call);
if (!reader.PopString(&id)) {
LOG(ERROR) << "Unable to read " << kSetPowerSourceMethod << " args";
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_INVALID_ARGS, "Expected string"));
return;
}
LOG(INFO) << "Received request to switch to power source \"" << id << "\"";
if (!SetPowerSource(id)) {
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
"Couldn't set power source"));
return;
}
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
bool PowerSupply::SetPowerSource(const std::string& id) {
// An empty ID means we should write -1 to any power source (we'll use the
// active one) to ask the kernel to use the battery as the power source.
// Otherwise, write 0 to the requested power source to activate it.
const base::FilePath device_path =
GetPathForId(id.empty() ? power_status_.external_power_source_id : id);
if (device_path.empty())
return false;
const base::FilePath limit_path =
device_path.Append(kChargeControlLimitMaxFile);
const std::string value = id.empty() ? "-1" : "0";
if (!util::WriteFileFully(limit_path, value.c_str(), value.size())) {
LOG(ERROR) << "Failed to write " << value << " to " << limit_path.value();
return false;
}
return true;
}
} // namespace system
} // namespace power_manager