| // Copyright (c) 2013 The Chromium 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 "ash/system/chromeos/power/power_status.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| |
| #include "ash/shell.h" |
| #include "ash/shell_delegate.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/power_manager_client.h" |
| #include "grit/ash_resources.h" |
| #include "grit/ash_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/l10n/time_format.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "ui/gfx/rect.h" |
| |
| namespace ash { |
| namespace internal { |
| |
| namespace { |
| |
| // Updates |proto| to ensure that its fields are consistent. |
| void SanitizeProto(power_manager::PowerSupplyProperties* proto) { |
| DCHECK(proto); |
| |
| if (proto->battery_state() == |
| power_manager::PowerSupplyProperties_BatteryState_FULL) |
| proto->set_battery_percent(100.0); |
| |
| if (!proto->is_calculating_battery_time()) { |
| const bool on_line_power = proto->external_power() != |
| power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED; |
| if ((on_line_power && proto->battery_time_to_full_sec() < 0) || |
| (!on_line_power && proto->battery_time_to_empty_sec() < 0)) |
| proto->set_is_calculating_battery_time(true); |
| } |
| } |
| |
| base::string16 GetBatteryTimeAccessibilityString(int hour, int min) { |
| DCHECK(hour || min); |
| if (hour && !min) { |
| return ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromHours(hour)); |
| } |
| if (min && !hour) { |
| return ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromMinutes(min)); |
| } |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_BATTERY_TIME_ACCESSIBLE, |
| ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromHours(hour)), |
| ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromMinutes(min))); |
| } |
| |
| static PowerStatus* g_power_status = NULL; |
| |
| // Minimum battery percentage rendered in UI. |
| const int kMinBatteryPercent = 1; |
| |
| // Width and height of battery images. |
| const int kBatteryImageHeight = 25; |
| const int kBatteryImageWidth = 25; |
| |
| // Number of different power states. |
| const int kNumPowerImages = 15; |
| |
| } // namespace |
| |
| const int PowerStatus::kMaxBatteryTimeToDisplaySec = 24 * 60 * 60; |
| |
| // static |
| void PowerStatus::Initialize() { |
| CHECK(!g_power_status); |
| g_power_status = new PowerStatus(); |
| } |
| |
| // static |
| void PowerStatus::Shutdown() { |
| CHECK(g_power_status); |
| delete g_power_status; |
| g_power_status = NULL; |
| } |
| |
| // static |
| bool PowerStatus::IsInitialized() { |
| return g_power_status != NULL; |
| } |
| |
| // static |
| PowerStatus* PowerStatus::Get() { |
| CHECK(g_power_status) << "PowerStatus::Get() called before Initialize()."; |
| return g_power_status; |
| } |
| |
| // static |
| bool PowerStatus::ShouldDisplayBatteryTime(const base::TimeDelta& time) { |
| return time >= base::TimeDelta::FromMinutes(1) && |
| time.InSeconds() <= kMaxBatteryTimeToDisplaySec; |
| } |
| |
| // static |
| void PowerStatus::SplitTimeIntoHoursAndMinutes(const base::TimeDelta& time, |
| int* hours, |
| int* minutes) { |
| DCHECK(hours); |
| DCHECK(minutes); |
| *hours = time.InHours(); |
| const double seconds = |
| (time - base::TimeDelta::FromHours(*hours)).InSecondsF(); |
| *minutes = static_cast<int>(seconds / 60.0 + 0.5); |
| } |
| |
| void PowerStatus::AddObserver(Observer* observer) { |
| DCHECK(observer); |
| observers_.AddObserver(observer); |
| } |
| |
| void PowerStatus::RemoveObserver(Observer* observer) { |
| DCHECK(observer); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void PowerStatus::RequestStatusUpdate() { |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> |
| RequestStatusUpdate(); |
| } |
| |
| bool PowerStatus::IsBatteryPresent() const { |
| return proto_.battery_state() != |
| power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT; |
| } |
| |
| bool PowerStatus::IsBatteryFull() const { |
| return proto_.battery_state() == |
| power_manager::PowerSupplyProperties_BatteryState_FULL; |
| } |
| |
| bool PowerStatus::IsBatteryCharging() const { |
| return proto_.battery_state() == |
| power_manager::PowerSupplyProperties_BatteryState_CHARGING; |
| } |
| |
| bool PowerStatus::IsBatteryDischargingOnLinePower() const { |
| return IsLinePowerConnected() && proto_.battery_state() == |
| power_manager::PowerSupplyProperties_BatteryState_DISCHARGING; |
| } |
| |
| double PowerStatus::GetBatteryPercent() const { |
| return proto_.battery_percent(); |
| } |
| |
| int PowerStatus::GetRoundedBatteryPercent() const { |
| return std::max(kMinBatteryPercent, |
| static_cast<int>(GetBatteryPercent() + 0.5)); |
| } |
| |
| bool PowerStatus::IsBatteryTimeBeingCalculated() const { |
| return proto_.is_calculating_battery_time(); |
| } |
| |
| base::TimeDelta PowerStatus::GetBatteryTimeToEmpty() const { |
| return base::TimeDelta::FromSeconds(proto_.battery_time_to_empty_sec()); |
| } |
| |
| base::TimeDelta PowerStatus::GetBatteryTimeToFull() const { |
| return base::TimeDelta::FromSeconds(proto_.battery_time_to_full_sec()); |
| } |
| |
| bool PowerStatus::IsLinePowerConnected() const { |
| return proto_.external_power() != |
| power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED; |
| } |
| |
| bool PowerStatus::IsMainsChargerConnected() const { |
| return proto_.external_power() == |
| power_manager::PowerSupplyProperties_ExternalPower_AC; |
| } |
| |
| bool PowerStatus::IsUsbChargerConnected() const { |
| return proto_.external_power() == |
| power_manager::PowerSupplyProperties_ExternalPower_USB; |
| } |
| |
| gfx::ImageSkia PowerStatus::GetBatteryImage(IconSet icon_set) const { |
| gfx::Image all; |
| if (IsUsbChargerConnected()) { |
| all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| icon_set == ICON_DARK ? |
| IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE_DARK : |
| IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE); |
| } else { |
| all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| icon_set == ICON_DARK ? |
| IDR_AURA_UBER_TRAY_POWER_SMALL_DARK : IDR_AURA_UBER_TRAY_POWER_SMALL); |
| } |
| |
| // Get the horizontal offset in the battery icon array image. The USB / |
| // "unreliable charging" image has a single column of icons; the other |
| // image contains a "battery" column on the left and a "line power" |
| // column on the right. |
| int offset = IsUsbChargerConnected() ? 0 : (IsLinePowerConnected() ? 1 : 0); |
| |
| // Get the vertical offset corresponding to the current battery level. |
| int index = -1; |
| if (GetBatteryPercent() >= 100.0) { |
| index = kNumPowerImages - 1; |
| } else if (!IsBatteryPresent()) { |
| index = kNumPowerImages; |
| } else { |
| index = static_cast<int>( |
| GetBatteryPercent() / 100.0 * (kNumPowerImages - 1)); |
| index = std::max(std::min(index, kNumPowerImages - 2), 0); |
| } |
| |
| gfx::Rect region( |
| offset * kBatteryImageWidth, index * kBatteryImageHeight, |
| kBatteryImageWidth, kBatteryImageHeight); |
| return gfx::ImageSkiaOperations::ExtractSubset(*all.ToImageSkia(), region); |
| } |
| |
| base::string16 PowerStatus::GetAccessibleNameString() const { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| if (IsBatteryFull()) { |
| return rb.GetLocalizedString( |
| IDS_ASH_STATUS_TRAY_BATTERY_FULL_CHARGE_ACCESSIBLE); |
| } |
| |
| base::string16 battery_percentage_accessible = l10n_util::GetStringFUTF16( |
| IsBatteryCharging() ? |
| IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_CHARGING_ACCESSIBLE : |
| IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ACCESSIBLE, |
| base::IntToString16(GetRoundedBatteryPercent())); |
| base::string16 battery_time_accessible = base::string16(); |
| const base::TimeDelta time = IsBatteryCharging() ? GetBatteryTimeToFull() : |
| GetBatteryTimeToEmpty(); |
| |
| if (IsUsbChargerConnected()) { |
| battery_time_accessible = rb.GetLocalizedString( |
| IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE); |
| } else if (IsBatteryTimeBeingCalculated()) { |
| battery_time_accessible = rb.GetLocalizedString( |
| IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE); |
| } else if (ShouldDisplayBatteryTime(time) && |
| !IsBatteryDischargingOnLinePower()) { |
| int hour = 0, min = 0; |
| PowerStatus::SplitTimeIntoHoursAndMinutes(time, &hour, &min); |
| base::string16 minute = min < 10 ? |
| ASCIIToUTF16("0") + base::IntToString16(min) : |
| base::IntToString16(min); |
| battery_time_accessible = |
| l10n_util::GetStringFUTF16( |
| IsBatteryCharging() ? |
| IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_ACCESSIBLE : |
| IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_ACCESSIBLE, |
| GetBatteryTimeAccessibilityString(hour, min)); |
| } |
| return battery_time_accessible.empty() ? |
| battery_percentage_accessible : |
| battery_percentage_accessible + ASCIIToUTF16(". ") + |
| battery_time_accessible; |
| } |
| |
| PowerStatus::PowerStatus() { |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> |
| AddObserver(this); |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> |
| RequestStatusUpdate(); |
| } |
| |
| PowerStatus::~PowerStatus() { |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> |
| RemoveObserver(this); |
| } |
| |
| void PowerStatus::SetProtoForTesting( |
| const power_manager::PowerSupplyProperties& proto) { |
| proto_ = proto; |
| SanitizeProto(&proto_); |
| } |
| |
| void PowerStatus::PowerChanged( |
| const power_manager::PowerSupplyProperties& proto) { |
| proto_ = proto; |
| SanitizeProto(&proto_); |
| FOR_EACH_OBSERVER(Observer, observers_, OnPowerStatusChanged()); |
| } |
| |
| } // namespace internal |
| } // namespace ash |