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 *minutes = (time - base::TimeDelta::FromHours(*hours)).InMinutes(); 116 } 117 118 void PowerStatus::AddObserver(Observer* observer) { 119 DCHECK(observer); 120 observers_.AddObserver(observer); 121 } 122 123 void PowerStatus::RemoveObserver(Observer* observer) { 124 DCHECK(observer); 125 observers_.RemoveObserver(observer); 126 } 127 128 void PowerStatus::RequestStatusUpdate() { 129 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 130 RequestStatusUpdate(); 131 } 132 133 bool PowerStatus::IsBatteryPresent() const { 134 return proto_.battery_state() != 135 power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT; 136 } 137 138 bool PowerStatus::IsBatteryFull() const { 139 return proto_.battery_state() == 140 power_manager::PowerSupplyProperties_BatteryState_FULL; 141 } 142 143 bool PowerStatus::IsBatteryCharging() const { 144 return proto_.battery_state() == 145 power_manager::PowerSupplyProperties_BatteryState_CHARGING; 146 } 147 148 bool PowerStatus::IsBatteryDischargingOnLinePower() const { 149 return IsLinePowerConnected() && proto_.battery_state() == 150 power_manager::PowerSupplyProperties_BatteryState_DISCHARGING; 151 } 152 153 double PowerStatus::GetBatteryPercent() const { 154 return proto_.battery_percent(); 155 } 156 157 int PowerStatus::GetRoundedBatteryPercent() const { 158 return std::max(kMinBatteryPercent, 159 static_cast<int>(GetBatteryPercent() + 0.5)); 160 } 161 162 bool PowerStatus::IsBatteryTimeBeingCalculated() const { 163 return proto_.is_calculating_battery_time(); 164 } 165 166 base::TimeDelta PowerStatus::GetBatteryTimeToEmpty() const { 167 return base::TimeDelta::FromSeconds(proto_.battery_time_to_empty_sec()); 168 } 169 170 base::TimeDelta PowerStatus::GetBatteryTimeToFull() const { 171 return base::TimeDelta::FromSeconds(proto_.battery_time_to_full_sec()); 172 } 173 174 bool PowerStatus::IsLinePowerConnected() const { 175 return proto_.external_power() != 176 power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED; 177 } 178 179 bool PowerStatus::IsMainsChargerConnected() const { 180 return proto_.external_power() == 181 power_manager::PowerSupplyProperties_ExternalPower_AC; 182 } 183 184 bool PowerStatus::IsUsbChargerConnected() const { 185 return proto_.external_power() == 186 power_manager::PowerSupplyProperties_ExternalPower_USB; 187 } 188 189 gfx::ImageSkia PowerStatus::GetBatteryImage(IconSet icon_set) const { 190 gfx::Image all; 191 if (IsUsbChargerConnected()) { 192 all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( 193 icon_set == ICON_DARK ? 194 IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE_DARK : 195 IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE); 196 } else { 197 all = ui::ResourceBundle::GetSharedInstance().GetImageNamed( 198 icon_set == ICON_DARK ? 199 IDR_AURA_UBER_TRAY_POWER_SMALL_DARK : IDR_AURA_UBER_TRAY_POWER_SMALL); 200 } 201 202 // Get the horizontal offset in the battery icon array image. The USB / 203 // "unreliable charging" image has a single column of icons; the other 204 // image contains a "battery" column on the left and a "line power" 205 // column on the right. 206 int offset = IsUsbChargerConnected() ? 0 : (IsLinePowerConnected() ? 1 : 0); 207 208 // Get the vertical offset corresponding to the current battery level. 209 int index = -1; 210 if (GetBatteryPercent() >= 100.0) { 211 index = kNumPowerImages - 1; 212 } else if (!IsBatteryPresent()) { 213 index = kNumPowerImages; 214 } else { 215 index = static_cast<int>( 216 GetBatteryPercent() / 100.0 * (kNumPowerImages - 1)); 217 index = std::max(std::min(index, kNumPowerImages - 2), 0); 218 } 219 220 gfx::Rect region( 221 offset * kBatteryImageWidth, index * kBatteryImageHeight, 222 kBatteryImageWidth, kBatteryImageHeight); 223 return gfx::ImageSkiaOperations::ExtractSubset(*all.ToImageSkia(), region); 224 } 225 226 base::string16 PowerStatus::GetAccessibleNameString() const { 227 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 228 if (IsBatteryFull()) { 229 return rb.GetLocalizedString( 230 IDS_ASH_STATUS_TRAY_BATTERY_FULL_CHARGE_ACCESSIBLE); 231 } 232 233 base::string16 battery_percentage_accessible = l10n_util::GetStringFUTF16( 234 IsBatteryCharging() ? 235 IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_CHARGING_ACCESSIBLE : 236 IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ACCESSIBLE, 237 base::IntToString16(GetRoundedBatteryPercent())); 238 base::string16 battery_time_accessible = base::string16(); 239 const base::TimeDelta time = IsBatteryCharging() ? GetBatteryTimeToFull() : 240 GetBatteryTimeToEmpty(); 241 242 if (IsUsbChargerConnected()) { 243 battery_time_accessible = rb.GetLocalizedString( 244 IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE); 245 } else if (IsBatteryTimeBeingCalculated()) { 246 battery_time_accessible = rb.GetLocalizedString( 247 IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE); 248 } else if (ShouldDisplayBatteryTime(time) && 249 !IsBatteryDischargingOnLinePower()) { 250 int hour = 0, min = 0; 251 PowerStatus::SplitTimeIntoHoursAndMinutes(time, &hour, &min); 252 base::string16 minute = min < 10 ? 253 ASCIIToUTF16("0") + base::IntToString16(min) : 254 base::IntToString16(min); 255 battery_time_accessible = 256 l10n_util::GetStringFUTF16( 257 IsBatteryCharging() ? 258 IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_ACCESSIBLE : 259 IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_ACCESSIBLE, 260 GetBatteryTimeAccessibilityString(hour, min)); 261 } 262 return battery_time_accessible.empty() ? 263 battery_percentage_accessible : 264 battery_percentage_accessible + ASCIIToUTF16(". ") + 265 battery_time_accessible; 266 } 267 268 PowerStatus::PowerStatus() { 269 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 270 AddObserver(this); 271 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 272 RequestStatusUpdate(); 273 } 274 275 PowerStatus::~PowerStatus() { 276 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 277 RemoveObserver(this); 278 } 279 280 void PowerStatus::SetProtoForTesting( 281 const power_manager::PowerSupplyProperties& proto) { 282 proto_ = proto; 283 SanitizeProto(&proto_); 284 } 285 286 void PowerStatus::PowerChanged( 287 const power_manager::PowerSupplyProperties& proto) { 288 proto_ = proto; 289 SanitizeProto(&proto_); 290 FOR_EACH_OBSERVER(Observer, observers_, OnPowerStatusChanged()); 291 } 292 293 } // namespace internal 294 } // namespace ash 295