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