| // 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 <cmath> |
| |
| #include <base/bind.h> |
| #include <base/file_util.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/message_loop/message_loop.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| |
| #include "power_manager/common/clock.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/udev.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 values for |battery_stabilized_after_*_delay_|, in milliseconds. |
| const int kDefaultBatteryStabilizedAfterStartupDelayMs = 5000; |
| const int kDefaultBatteryStabilizedAfterLinePowerConnectedDelayMs = 5000; |
| const int kDefaultBatteryStabilizedAfterLinePowerDisconnectedDelayMs = 5000; |
| const int kDefaultBatteryStabilizedAfterResumeDelayMs = 5000; |
| |
| // Different power supply types reported by the kernel. |
| const char kBatteryType[] = "Battery"; |
| const char kMainsType[] = "Mains"; |
| |
| // Battery states reported by the kernel. This is not the full set of |
| // possible states; see drivers/power/power_supply_sysfs.c. |
| const char kBatteryStatusCharging[] = "Charging"; |
| const char kBatteryStatusFull[] = "Full"; |
| |
| // String value reported in the line power "model_name" file if an original |
| // spring AC charger is connected. |
| const char kOriginalSpringChargerModelName[] = "0x17"; |
| |
| // String value reported in the line power "model_name" file if the EC firmware |
| // is outdated and is incapable of reporting the model. |
| const char kOldFirmwareModelName[] = "0x00"; |
| |
| // 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) { |
| if (!base::ReadFileToString(directory.Append(filename), out)) |
| return false; |
| |
| base::TrimWhitespaceASCII(*out, base::TRIM_TRAILING, out); |
| return true; |
| } |
| |
| // 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* 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 value = 0; |
| return ReadInt64(directory, filename, &value) ? |
| kDoubleScaleFactor * value : 0.0; |
| } |
| |
| // Returns true if |type|, a power supply type read from a "type" file in |
| // sysfs, indicates USB. |
| bool IsUsbType(const std::string& type) { |
| // These are defined in drivers/power/power_supply_sysfs.c in the kernel. |
| return type == "USB" || type == "USB_DCP" || type == "USB_CDP" || |
| type == "USB_ACA"; |
| } |
| |
| // Returns true if |model_name|, a line power source's model, indicates that an |
| // original spring AC charger is connected. Also assumes that an original spring |
| // AC charger is connected if the firmware is outdated and doesn't report the |
| // model. |
| bool IsOriginalSpringCharger(const std::string& model_name) { |
| return model_name == kOriginalSpringChargerModelName || |
| model_name == kOldFirmwareModelName; |
| } |
| |
| } // namespace |
| |
| 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_->HandlePollTimeout(); |
| return true; |
| } |
| |
| const char PowerSupply::kUdevSubsystem[] = "power_supply"; |
| const int PowerSupply::kMaxCurrentSamples = 5; |
| const int PowerSupply::kMaxChargeSamples = 5; |
| const int PowerSupply::kObservedBatteryChargeRateMinMs = kDefaultPollMs; |
| const int PowerSupply::kBatteryStabilizedSlackMs = 50; |
| const double PowerSupply::kLowBatteryShutdownSafetyPercent = 5.0; |
| |
| PowerSupply::PowerSupply() |
| : prefs_(NULL), |
| udev_(NULL), |
| clock_(new Clock), |
| power_status_initialized_(false), |
| low_battery_shutdown_percent_(0.0), |
| is_suspended_(false), |
| current_samples_(kMaxCurrentSamples), |
| charge_samples_(kMaxChargeSamples), |
| full_factor_(1.0) { |
| } |
| |
| PowerSupply::~PowerSupply() { |
| if (udev_) |
| udev_->RemoveObserver(kUdevSubsystem, this); |
| } |
| |
| void PowerSupply::Init(const base::FilePath& power_supply_path, |
| PrefsInterface* prefs, |
| UdevInterface* udev) { |
| udev_ = udev; |
| udev_->AddObserver(kUdevSubsystem, this); |
| |
| prefs_ = prefs; |
| power_supply_path_ = power_supply_path; |
| GetPowerSupplyPaths(); |
| |
| poll_delay_ = GetMsPref(kBatteryPollIntervalPref, kDefaultPollMs); |
| 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); |
| |
| int64 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. |
| if (prefs_->GetDouble(kLowBatteryShutdownPercentPref, |
| &low_battery_shutdown_percent_)) { |
| low_battery_shutdown_time_ = base::TimeDelta(); |
| } |
| |
| // This log message is needed by the power_LoadTest autotest. |
| 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_; |
| |
| // This defers the initial recording of samples until the current has |
| // stabilized. |
| ResetBatterySamples(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); |
| } |
| |
| bool PowerSupply::RefreshImmediately() { |
| const bool success = UpdatePowerStatus(); |
| if (!is_suspended_) |
| SchedulePoll(); |
| if (success) { |
| notify_observers_task_.Reset( |
| base::Bind(&PowerSupply::NotifyObservers, base::Unretained(this))); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, notify_observers_task_.callback()); |
| } |
| return success; |
| } |
| |
| 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 { |
| ResetBatterySamples(battery_stabilized_after_resume_delay_); |
| RefreshImmediately(); |
| } |
| } |
| |
| void PowerSupply::OnUdevEvent(const std::string& subsystem, |
| const std::string& sysname, |
| UdevObserver::Action action) { |
| VLOG(1) << "Heard about udev event"; |
| if (!is_suspended_) |
| RefreshImmediately(); |
| } |
| |
| base::TimeDelta PowerSupply::GetMsPref(const std::string& pref_name, |
| int64 default_duration_ms) const { |
| prefs_->GetInt64(pref_name, &default_duration_ms); |
| return base::TimeDelta::FromMilliseconds(default_duration_ms); |
| } |
| |
| void PowerSupply::ResetBatterySamples(base::TimeDelta stabilized_delay) { |
| current_samples_.Clear(); |
| charge_samples_.Clear(); |
| |
| 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() { |
| VLOG(1) << "Updating power status"; |
| PowerStatus status; |
| |
| if (battery_path_.empty() || line_power_path_.empty()) |
| GetPowerSupplyPaths(); |
| |
| status.line_power_path = line_power_path_.value(); |
| status.battery_path = battery_path_.value(); |
| |
| std::string battery_status_string; |
| if (base::PathExists(battery_path_)) { |
| int64 present_value = 0; |
| ReadInt64(battery_path_, "present", &present_value); |
| status.battery_is_present = present_value != 0; |
| if (status.battery_is_present) |
| ReadAndTrimString(battery_path_, "status", &battery_status_string); |
| } else { |
| status.battery_is_present = false; |
| } |
| |
| if (base::PathExists(line_power_path_)) { |
| int64 online_value = 0; |
| ReadInt64(line_power_path_, "online", &online_value); |
| status.line_power_on = online_value != 0; |
| ReadAndTrimString(line_power_path_, "type", &status.line_power_type); |
| ReadAndTrimString(line_power_path_, "model_name", |
| &status.line_power_model_name); |
| |
| if (status.line_power_on) { |
| status.external_power = IsUsbType(status.line_power_type) ? |
| PowerSupplyProperties_ExternalPower_USB : |
| (IsOriginalSpringCharger(status.line_power_model_name) ? |
| PowerSupplyProperties_ExternalPower_ORIGINAL_SPRING_CHARGER : |
| PowerSupplyProperties_ExternalPower_AC); |
| status.line_power_voltage = |
| ReadScaledDouble(line_power_path_, "voltage_now"); |
| status.line_power_current = |
| ReadScaledDouble(line_power_path_, "current_now"); |
| } else { |
| status.external_power = PowerSupplyProperties_ExternalPower_DISCONNECTED; |
| } |
| } else if (!status.battery_is_present) { |
| // If there's no battery, assume that the system is on AC power. |
| status.line_power_on = true; |
| status.line_power_type = kMainsType; |
| status.external_power = PowerSupplyProperties_ExternalPower_AC; |
| } else { |
| // Otherwise, infer the external power source from the kernel-reported |
| // battery status. |
| status.line_power_on = |
| battery_status_string == kBatteryStatusCharging || |
| battery_status_string == kBatteryStatusFull; |
| status.external_power = status.line_power_on ? |
| PowerSupplyProperties_ExternalPower_AC : |
| PowerSupplyProperties_ExternalPower_DISCONNECTED; |
| } |
| |
| // The rest of the calculations all require a battery. |
| if (!status.battery_is_present) { |
| status.battery_state = PowerSupplyProperties_BatteryState_NOT_PRESENT; |
| power_status_ = status; |
| power_status_initialized_ = true; |
| return true; |
| } |
| |
| // POWER_SUPPLY_PROP_VENDOR does not seem to be a valid property |
| // defined in <linux/power_supply.h>. |
| if (base::PathExists(battery_path_.Append("manufacturer"))) |
| ReadAndTrimString(battery_path_, "manufacturer", &status.battery_vendor); |
| else |
| ReadAndTrimString(battery_path_, "vendor", &status.battery_vendor); |
| ReadAndTrimString(battery_path_, "model_name", &status.battery_model_name); |
| ReadAndTrimString(battery_path_, "serial_number", &status.battery_serial); |
| ReadAndTrimString(battery_path_, "technology", &status.battery_technology); |
| |
| double battery_voltage = ReadScaledDouble(battery_path_, "voltage_now"); |
| status.battery_voltage = battery_voltage; |
| |
| // Attempt to determine nominal voltage for time remaining calculations. |
| // The battery voltage used in calculating time remaining. 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. |
| double nominal_voltage = 0.0; |
| if (base::PathExists(battery_path_.Append("voltage_min_design"))) |
| nominal_voltage = ReadScaledDouble(battery_path_, "voltage_min_design"); |
| else if (base::PathExists(battery_path_.Append("voltage_max_design"))) |
| nominal_voltage = ReadScaledDouble(battery_path_, "voltage_max_design"); |
| |
| // Nominal voltage is not required to obtain charge level. If it is missing, |
| // just log a message, set to |battery_voltage| so time remaining |
| // calculations will function, and proceed. |
| if (nominal_voltage <= 0) { |
| LOG(WARNING) << "Invalid voltage_min/max_design reading: " |
| << nominal_voltage << "V." |
| << " Time remaining calculations will not be available."; |
| nominal_voltage = battery_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. |
| double battery_charge_full = 0; |
| double battery_charge_full_design = 0; |
| double battery_charge = 0; |
| |
| if (base::PathExists(battery_path_.Append("charge_full"))) { |
| battery_charge_full = ReadScaledDouble(battery_path_, "charge_full"); |
| battery_charge_full_design = |
| ReadScaledDouble(battery_path_, "charge_full_design"); |
| battery_charge = ReadScaledDouble(battery_path_, "charge_now"); |
| } else if (base::PathExists(battery_path_.Append("energy_full"))) { |
| // Valid |battery_voltage| is required to determine the charge so return |
| // early if it is not present. In this case, we know nothing about |
| // battery state or remaining percentage, so set proper status. |
| if (battery_voltage <= 0) { |
| LOG(WARNING) << "Invalid voltage_now reading for energy-to-charge" |
| << " conversion: " << battery_voltage; |
| return false; |
| } |
| battery_charge_full = |
| ReadScaledDouble(battery_path_, "energy_full") / battery_voltage; |
| battery_charge_full_design = |
| ReadScaledDouble(battery_path_, "energy_full_design") / battery_voltage; |
| battery_charge = |
| ReadScaledDouble(battery_path_, "energy_now") / battery_voltage; |
| } else { |
| LOG(WARNING) << "No charge/energy readings for battery"; |
| return false; |
| } |
| |
| status.battery_charge_full = battery_charge_full; |
| status.battery_charge_full_design = battery_charge_full_design; |
| status.battery_charge = battery_charge; |
| if (status.battery_charge_full <= 0.0) { |
| LOG(WARNING) << "Got battery-full charge of " << status.battery_charge_full; |
| return false; |
| } |
| |
| // 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 battery_current = 0; |
| if (base::PathExists(battery_path_.Append("power_now"))) { |
| battery_current = fabs(ReadScaledDouble(battery_path_, "power_now")) / |
| battery_voltage; |
| } else { |
| battery_current = fabs(ReadScaledDouble(battery_path_, "current_now")); |
| } |
| status.battery_current = battery_current; |
| |
| status.battery_energy = battery_charge * nominal_voltage; |
| status.battery_energy_rate = battery_current * battery_voltage; |
| |
| status.battery_percentage = util::ClampPercent( |
| 100.0 * battery_charge / battery_charge_full); |
| status.display_battery_percentage = util::ClampPercent( |
| 100.0 * (status.battery_percentage - low_battery_shutdown_percent_) / |
| (100.0 * full_factor_ - low_battery_shutdown_percent_)); |
| |
| const bool battery_is_full = |
| battery_charge >= battery_charge_full * full_factor_; |
| |
| if (status.line_power_on) { |
| if (battery_is_full) { |
| status.battery_state = PowerSupplyProperties_BatteryState_FULL; |
| } else if (battery_current > 0.0 && |
| (battery_status_string == kBatteryStatusCharging || |
| battery_status_string == kBatteryStatusFull)) { |
| status.battery_state = PowerSupplyProperties_BatteryState_CHARGING; |
| } else { |
| status.battery_state = PowerSupplyProperties_BatteryState_DISCHARGING; |
| } |
| } else { |
| status.battery_state = PowerSupplyProperties_BatteryState_DISCHARGING; |
| } |
| |
| if (power_status_initialized_ && |
| status.line_power_on != power_status_.line_power_on) { |
| ResetBatterySamples(status.line_power_on ? |
| battery_stabilized_after_line_power_connected_delay_ : |
| battery_stabilized_after_line_power_disconnected_delay_); |
| } |
| |
| base::TimeTicks now = clock_->GetCurrentTime(); |
| if (now >= battery_stabilized_timestamp_) { |
| charge_samples_.AddSample(battery_charge, now); |
| if (battery_current > 0.0) |
| current_samples_.AddSample(battery_current, now); |
| } |
| |
| 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::GetPowerSupplyPaths() { |
| // First check if both line power and battery paths have been found and still |
| // exist. If so, there is no need to do anything else. |
| if (base::PathExists(battery_path_) && base::PathExists(line_power_path_)) |
| return; |
| // Use a FileEnumerator to browse through all files/subdirectories in the |
| // power supply sysfs directory. |
| base::FileEnumerator file_enum( |
| power_supply_path_, false, base::FileEnumerator::DIRECTORIES); |
| // Read type info from all power sources, and try to identify battery and line |
| // power sources. Their paths are to be stored locally. |
| for (base::FilePath path = file_enum.Next(); |
| !path.empty(); |
| path = file_enum.Next()) { |
| // External devices have "scope" attributes containing the value "Device". |
| // Skip them. |
| base::FilePath scope_path = path.Append("scope"); |
| if (base::PathExists(scope_path)) { |
| std::string buf; |
| base::ReadFileToString(scope_path, &buf); |
| base::TrimWhitespaceASCII(buf, base::TRIM_TRAILING, &buf); |
| if (buf == "Device") { |
| VLOG(1) << "Skipping Power supply " << path.value() |
| << " with scope: " << buf; |
| continue; |
| } |
| } |
| std::string buf; |
| if (base::ReadFileToString(path.Append("type"), &buf)) { |
| base::TrimWhitespaceASCII(buf, base::TRIM_TRAILING, &buf); |
| // Only look for battery / line power paths if they haven't been found |
| // already. This makes the assumption that they don't change (but battery |
| // path can disappear if removed). So this code should only be run once |
| // for each power source. |
| if (buf == kBatteryType && battery_path_.empty()) { |
| VLOG(1) << "Battery path found: " << path.value(); |
| battery_path_ = path; |
| } else if (buf != kBatteryType && line_power_path_.empty()) { |
| VLOG(1) << "Line power path found: " << path.value(); |
| line_power_path_ = path; |
| } |
| } |
| } |
| } |
| |
| 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 (clock_->GetCurrentTime() < battery_stabilized_timestamp_) |
| return false; |
| |
| const double average_current = current_samples_.GetAverage(); |
| switch (status->battery_state) { |
| case PowerSupplyProperties_BatteryState_CHARGING: |
| if (average_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 / average_current)); |
| } |
| break; |
| case PowerSupplyProperties_BatteryState_DISCHARGING: |
| if (average_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) / |
| (average_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) / |
| (average_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 " << 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; |
| |
| // TODO(derat): Figure out what's causing http://crosbug.com/38912. |
| if (status.battery_percentage <= kEpsilon) { |
| LOG(WARNING) << "Ignoring probably-bogus zero battery percentage"; |
| 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; |
| } |
| |
| void PowerSupply::SchedulePoll() { |
| base::TimeDelta delay = poll_delay_; |
| base::TimeTicks now = clock_->GetCurrentTime(); |
| if (battery_stabilized_timestamp_ > now) { |
| delay = std::min(delay, |
| battery_stabilized_timestamp_ - now + |
| base::TimeDelta::FromMilliseconds(kBatteryStabilizedSlackMs)); |
| } |
| |
| VLOG(1) << "Scheduling update in " << delay.InMilliseconds() << " ms"; |
| poll_timer_.Start(FROM_HERE, delay, this, &PowerSupply::HandlePollTimeout); |
| current_poll_delay_for_testing_ = delay; |
| } |
| |
| void PowerSupply::HandlePollTimeout() { |
| current_poll_delay_for_testing_ = base::TimeDelta(); |
| const bool success = UpdatePowerStatus(); |
| SchedulePoll(); |
| if (success) |
| NotifyObservers(); |
| } |
| |
| void PowerSupply::NotifyObservers() { |
| FOR_EACH_OBSERVER(PowerSupplyObserver, observers_, OnPowerStatusUpdate()); |
| } |
| |
| } // namespace system |
| } // namespace power_manager |