| // 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 |