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   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