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