Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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" |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 20 | #include "ui/base/l10n/time_format.h" |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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) { |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 51 | return ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromHours(hour)); |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 52 | } |
| 53 | if (min && !hour) { |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 54 | return ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromMinutes(min)); |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 55 | } |
| 56 | return l10n_util::GetStringFUTF16( |
| 57 | IDS_ASH_STATUS_TRAY_BATTERY_TIME_ACCESSIBLE, |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 58 | ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromHours(hour)), |
| 59 | ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromMinutes(min))); |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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 | |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 76 | const int PowerStatus::kMaxBatteryTimeToDisplaySec = 24 * 60 * 60; |
| 77 | |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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 | |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 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 | |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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 | |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 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 | |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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 | |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 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); |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 207 | |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 208 | // Get the vertical offset corresponding to the current battery level. |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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(); |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 228 | if (IsBatteryFull()) { |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 229 | return rb.GetLocalizedString( |
| 230 | IDS_ASH_STATUS_TRAY_BATTERY_FULL_CHARGE_ACCESSIBLE); |
| 231 | } |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 232 | |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 233 | base::string16 battery_percentage_accessible = l10n_util::GetStringFUTF16( |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 234 | IsBatteryCharging() ? |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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(); |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 239 | const base::TimeDelta time = IsBatteryCharging() ? GetBatteryTimeToFull() : |
| 240 | GetBatteryTimeToEmpty(); |
| 241 | |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 242 | if (IsUsbChargerConnected()) { |
| 243 | battery_time_accessible = rb.GetLocalizedString( |
| 244 | IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE); |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 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)); |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 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 |