// 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 #include #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 { // 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::Simple(ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG, base::TimeDelta::FromHours(hour)); } if (min && !hour) { return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG, base::TimeDelta::FromMinutes(min)); } return l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_BATTERY_TIME_ACCESSIBLE, ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG, base::TimeDelta::FromHours(hour)), ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG, 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); const int total_minutes = static_cast(time.InSecondsF() / 60 + 0.5); *hours = total_minutes / 60; *minutes = total_minutes % 60; } 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(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; } bool PowerStatus::IsOriginalSpringChargerConnected() const { return proto_.external_power() == power_manager:: PowerSupplyProperties_ExternalPower_ORIGINAL_SPRING_CHARGER; } 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( 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 ? base::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 + base::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 ash