Home | History | Annotate | Download | only in power
      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