1// Copyright (c) 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ash/system/chromeos/power/power_status.h" 6 7#include <algorithm> 8#include <cmath> 9 10#include "ash/shell.h" 11#include "ash/shell_delegate.h" 12#include "base/logging.h" 13#include "base/strings/string_number_conversions.h" 14#include "base/strings/utf_string_conversions.h" 15#include "chromeos/dbus/dbus_thread_manager.h" 16#include "chromeos/dbus/power_manager_client.h" 17#include "grit/ash_resources.h" 18#include "grit/ash_strings.h" 19#include "ui/base/l10n/l10n_util.h" 20#include "ui/base/l10n/time_format.h" 21#include "ui/base/resource/resource_bundle.h" 22#include "ui/gfx/image/image.h" 23#include "ui/gfx/image/image_skia_operations.h" 24#include "ui/gfx/rect.h" 25 26namespace ash { 27namespace { 28 29// Updates |proto| to ensure that its fields are consistent. 30void SanitizeProto(power_manager::PowerSupplyProperties* proto) { 31 DCHECK(proto); 32 33 if (proto->battery_state() == 34 power_manager::PowerSupplyProperties_BatteryState_FULL) 35 proto->set_battery_percent(100.0); 36 37 if (!proto->is_calculating_battery_time()) { 38 const bool on_line_power = proto->external_power() != 39 power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED; 40 if ((on_line_power && proto->battery_time_to_full_sec() < 0) || 41 (!on_line_power && proto->battery_time_to_empty_sec() < 0)) 42 proto->set_is_calculating_battery_time(true); 43 } 44} 45 46base::string16 GetBatteryTimeAccessibilityString(int hour, int min) { 47 DCHECK(hour || min); 48 if (hour && !min) { 49 return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, 50 ui::TimeFormat::LENGTH_LONG, 51 base::TimeDelta::FromHours(hour)); 52 } 53 if (min && !hour) { 54 return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, 55 ui::TimeFormat::LENGTH_LONG, 56 base::TimeDelta::FromMinutes(min)); 57 } 58 return l10n_util::GetStringFUTF16( 59 IDS_ASH_STATUS_TRAY_BATTERY_TIME_ACCESSIBLE, 60 ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, 61 ui::TimeFormat::LENGTH_LONG, 62 base::TimeDelta::FromHours(hour)), 63 ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, 64 ui::TimeFormat::LENGTH_LONG, 65 base::TimeDelta::FromMinutes(min))); 66} 67 68static PowerStatus* g_power_status = NULL; 69 70// Minimum battery percentage rendered in UI. 71const int kMinBatteryPercent = 1; 72 73// Width and height of battery images. 74const int kBatteryImageHeight = 25; 75const int kBatteryImageWidth = 25; 76 77// Number of different power states. 78const int kNumPowerImages = 15; 79 80} // namespace 81 82const int PowerStatus::kMaxBatteryTimeToDisplaySec = 24 * 60 * 60; 83 84// static 85void PowerStatus::Initialize() { 86 CHECK(!g_power_status); 87 g_power_status = new PowerStatus(); 88} 89 90// static 91void PowerStatus::Shutdown() { 92 CHECK(g_power_status); 93 delete g_power_status; 94 g_power_status = NULL; 95} 96 97// static 98bool PowerStatus::IsInitialized() { 99 return g_power_status != NULL; 100} 101 102// static 103PowerStatus* PowerStatus::Get() { 104 CHECK(g_power_status) << "PowerStatus::Get() called before Initialize()."; 105 return g_power_status; 106} 107 108// static 109bool PowerStatus::ShouldDisplayBatteryTime(const base::TimeDelta& time) { 110 return time >= base::TimeDelta::FromMinutes(1) && 111 time.InSeconds() <= kMaxBatteryTimeToDisplaySec; 112} 113 114// static 115void PowerStatus::SplitTimeIntoHoursAndMinutes(const base::TimeDelta& time, 116 int* hours, 117 int* minutes) { 118 DCHECK(hours); 119 DCHECK(minutes); 120 const int total_minutes = static_cast<int>(time.InSecondsF() / 60 + 0.5); 121 *hours = total_minutes / 60; 122 *minutes = total_minutes % 60; 123} 124 125void PowerStatus::AddObserver(Observer* observer) { 126 DCHECK(observer); 127 observers_.AddObserver(observer); 128} 129 130void PowerStatus::RemoveObserver(Observer* observer) { 131 DCHECK(observer); 132 observers_.RemoveObserver(observer); 133} 134 135void PowerStatus::RequestStatusUpdate() { 136 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 137 RequestStatusUpdate(); 138} 139 140bool PowerStatus::IsBatteryPresent() const { 141 return proto_.battery_state() != 142 power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT; 143} 144 145bool PowerStatus::IsBatteryFull() const { 146 return proto_.battery_state() == 147 power_manager::PowerSupplyProperties_BatteryState_FULL; 148} 149 150bool PowerStatus::IsBatteryCharging() const { 151 return proto_.battery_state() == 152 power_manager::PowerSupplyProperties_BatteryState_CHARGING; 153} 154 155bool PowerStatus::IsBatteryDischargingOnLinePower() const { 156 return IsLinePowerConnected() && proto_.battery_state() == 157 power_manager::PowerSupplyProperties_BatteryState_DISCHARGING; 158} 159 160double PowerStatus::GetBatteryPercent() const { 161 return proto_.battery_percent(); 162} 163 164int PowerStatus::GetRoundedBatteryPercent() const { 165 return std::max(kMinBatteryPercent, 166 static_cast<int>(GetBatteryPercent() + 0.5)); 167} 168 169bool PowerStatus::IsBatteryTimeBeingCalculated() const { 170 return proto_.is_calculating_battery_time(); 171} 172 173base::TimeDelta PowerStatus::GetBatteryTimeToEmpty() const { 174 return base::TimeDelta::FromSeconds(proto_.battery_time_to_empty_sec()); 175} 176 177base::TimeDelta PowerStatus::GetBatteryTimeToFull() const { 178 return base::TimeDelta::FromSeconds(proto_.battery_time_to_full_sec()); 179} 180 181bool PowerStatus::IsLinePowerConnected() const { 182 return proto_.external_power() != 183 power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED; 184} 185 186bool PowerStatus::IsMainsChargerConnected() const { 187 return proto_.external_power() == 188 power_manager::PowerSupplyProperties_ExternalPower_AC; 189} 190 191bool PowerStatus::IsUsbChargerConnected() const { 192 return proto_.external_power() == 193 power_manager::PowerSupplyProperties_ExternalPower_USB; 194} 195 196bool PowerStatus::IsOriginalSpringChargerConnected() const { 197 return proto_.external_power() == power_manager:: 198 PowerSupplyProperties_ExternalPower_ORIGINAL_SPRING_CHARGER; 199} 200 201gfx::ImageSkia PowerStatus::GetBatteryImage(IconSet icon_set) const { 202 gfx::Image all; 203 if (IsUsbChargerConnected()) { 204 all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( 205 icon_set == ICON_DARK ? 206 IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE_DARK : 207 IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE); 208 } else { 209 all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( 210 icon_set == ICON_DARK ? 211 IDR_AURA_UBER_TRAY_POWER_SMALL_DARK : IDR_AURA_UBER_TRAY_POWER_SMALL); 212 } 213 214 // Get the horizontal offset in the battery icon array image. The USB / 215 // "unreliable charging" image has a single column of icons; the other 216 // image contains a "battery" column on the left and a "line power" 217 // column on the right. 218 int offset = IsUsbChargerConnected() ? 0 : (IsLinePowerConnected() ? 1 : 0); 219 220 // Get the vertical offset corresponding to the current battery level. 221 int index = -1; 222 if (GetBatteryPercent() >= 100.0) { 223 index = kNumPowerImages - 1; 224 } else if (!IsBatteryPresent()) { 225 index = kNumPowerImages; 226 } else { 227 index = static_cast<int>( 228 GetBatteryPercent() / 100.0 * (kNumPowerImages - 1)); 229 index = std::max(std::min(index, kNumPowerImages - 2), 0); 230 } 231 232 gfx::Rect region( 233 offset * kBatteryImageWidth, index * kBatteryImageHeight, 234 kBatteryImageWidth, kBatteryImageHeight); 235 return gfx::ImageSkiaOperations::ExtractSubset(*all.ToImageSkia(), region); 236} 237 238base::string16 PowerStatus::GetAccessibleNameString( 239 bool full_description) const { 240 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 241 if (IsBatteryFull()) { 242 return rb.GetLocalizedString( 243 IDS_ASH_STATUS_TRAY_BATTERY_FULL_CHARGE_ACCESSIBLE); 244 } 245 246 base::string16 battery_percentage_accessible = l10n_util::GetStringFUTF16( 247 IsBatteryCharging() ? 248 IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_CHARGING_ACCESSIBLE : 249 IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ACCESSIBLE, 250 base::IntToString16(GetRoundedBatteryPercent())); 251 if (!full_description) 252 return battery_percentage_accessible; 253 254 base::string16 battery_time_accessible = base::string16(); 255 const base::TimeDelta time = IsBatteryCharging() ? GetBatteryTimeToFull() : 256 GetBatteryTimeToEmpty(); 257 258 if (IsUsbChargerConnected()) { 259 battery_time_accessible = rb.GetLocalizedString( 260 IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE); 261 } else if (IsBatteryTimeBeingCalculated()) { 262 battery_time_accessible = rb.GetLocalizedString( 263 IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE); 264 } else if (ShouldDisplayBatteryTime(time) && 265 !IsBatteryDischargingOnLinePower()) { 266 int hour = 0, min = 0; 267 PowerStatus::SplitTimeIntoHoursAndMinutes(time, &hour, &min); 268 base::string16 minute = min < 10 ? 269 base::ASCIIToUTF16("0") + base::IntToString16(min) : 270 base::IntToString16(min); 271 battery_time_accessible = 272 l10n_util::GetStringFUTF16( 273 IsBatteryCharging() ? 274 IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_ACCESSIBLE : 275 IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_ACCESSIBLE, 276 GetBatteryTimeAccessibilityString(hour, min)); 277 } 278 return battery_time_accessible.empty() ? 279 battery_percentage_accessible : 280 battery_percentage_accessible + base::ASCIIToUTF16(". ") + 281 battery_time_accessible; 282} 283 284PowerStatus::PowerStatus() { 285 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 286 AddObserver(this); 287 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 288 RequestStatusUpdate(); 289} 290 291PowerStatus::~PowerStatus() { 292 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 293 RemoveObserver(this); 294} 295 296void PowerStatus::SetProtoForTesting( 297 const power_manager::PowerSupplyProperties& proto) { 298 proto_ = proto; 299 SanitizeProto(&proto_); 300} 301 302void PowerStatus::PowerChanged( 303 const power_manager::PowerSupplyProperties& proto) { 304 proto_ = proto; 305 SanitizeProto(&proto_); 306 FOR_EACH_OBSERVER(Observer, observers_, OnPowerStatusChanged()); 307} 308 309} // namespace ash 310