Home | History | Annotate | Download | only in power
      1 // Copyright (c) 2012 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/tray_power.h"
      6 
      7 #include "ash/ash_switches.h"
      8 #include "ash/shell.h"
      9 #include "ash/system/chromeos/power/power_status_view.h"
     10 #include "ash/system/date/date_view.h"
     11 #include "ash/system/system_notifier.h"
     12 #include "ash/system/tray/system_tray_delegate.h"
     13 #include "ash/system/tray/tray_constants.h"
     14 #include "ash/system/tray/tray_notification_view.h"
     15 #include "ash/system/tray/tray_utils.h"
     16 #include "base/command_line.h"
     17 #include "base/metrics/histogram.h"
     18 #include "grit/ash_resources.h"
     19 #include "grit/ash_strings.h"
     20 #include "third_party/icu/source/i18n/unicode/fieldpos.h"
     21 #include "third_party/icu/source/i18n/unicode/fmtable.h"
     22 #include "ui/base/accessibility/accessible_view_state.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 #include "ui/message_center/message_center.h"
     25 #include "ui/message_center/notification.h"
     26 #include "ui/views/controls/button/button.h"
     27 #include "ui/views/controls/image_view.h"
     28 #include "ui/views/controls/label.h"
     29 #include "ui/views/layout/box_layout.h"
     30 #include "ui/views/layout/fill_layout.h"
     31 #include "ui/views/layout/grid_layout.h"
     32 #include "ui/views/view.h"
     33 #include "ui/views/widget/widget.h"
     34 
     35 using message_center::MessageCenter;
     36 using message_center::Notification;
     37 
     38 namespace ash {
     39 namespace internal {
     40 namespace tray {
     41 
     42 // This view is used only for the tray.
     43 class PowerTrayView : public views::ImageView {
     44  public:
     45   PowerTrayView() {
     46     UpdateImage();
     47   }
     48 
     49   virtual ~PowerTrayView() {
     50   }
     51 
     52   // Overriden from views::View.
     53   virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
     54     state->name = accessible_name_;
     55     state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
     56   }
     57 
     58   void UpdateStatus(bool battery_alert) {
     59     UpdateImage();
     60     SetVisible(PowerStatus::Get()->IsBatteryPresent());
     61 
     62     if (battery_alert) {
     63       accessible_name_ = PowerStatus::Get()->GetAccessibleNameString();
     64       NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
     65     }
     66   }
     67 
     68  private:
     69   void UpdateImage() {
     70     SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT));
     71   }
     72 
     73   base::string16 accessible_name_;
     74 
     75   DISALLOW_COPY_AND_ASSIGN(PowerTrayView);
     76 };
     77 
     78 class PowerNotificationView : public TrayNotificationView {
     79  public:
     80   explicit PowerNotificationView(TrayPower* owner)
     81       : TrayNotificationView(owner, 0) {
     82     power_status_view_ =
     83         new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true);
     84     InitView(power_status_view_);
     85   }
     86 
     87   void UpdateStatus() {
     88     SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK));
     89   }
     90 
     91  private:
     92   PowerStatusView* power_status_view_;
     93 
     94   DISALLOW_COPY_AND_ASSIGN(PowerNotificationView);
     95 };
     96 
     97 }  // namespace tray
     98 
     99 using tray::PowerNotificationView;
    100 
    101 const int TrayPower::kCriticalMinutes = 5;
    102 const int TrayPower::kLowPowerMinutes = 15;
    103 const int TrayPower::kNoWarningMinutes = 30;
    104 const int TrayPower::kCriticalPercentage = 5;
    105 const int TrayPower::kLowPowerPercentage = 10;
    106 const int TrayPower::kNoWarningPercentage = 15;
    107 
    108 TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center)
    109     : SystemTrayItem(system_tray),
    110       message_center_(message_center),
    111       power_tray_(NULL),
    112       notification_view_(NULL),
    113       notification_state_(NOTIFICATION_NONE),
    114       usb_charger_was_connected_(false),
    115       line_power_was_connected_(false) {
    116   PowerStatus::Get()->AddObserver(this);
    117 }
    118 
    119 TrayPower::~TrayPower() {
    120   PowerStatus::Get()->RemoveObserver(this);
    121 }
    122 
    123 views::View* TrayPower::CreateTrayView(user::LoginStatus status) {
    124   // There may not be enough information when this is created about whether
    125   // there is a battery or not. So always create this, and adjust visibility as
    126   // necessary.
    127   CHECK(power_tray_ == NULL);
    128   power_tray_ = new tray::PowerTrayView();
    129   power_tray_->UpdateStatus(false);
    130   return power_tray_;
    131 }
    132 
    133 views::View* TrayPower::CreateDefaultView(user::LoginStatus status) {
    134   // Make sure icon status is up-to-date. (Also triggers stub activation).
    135   PowerStatus::Get()->RequestStatusUpdate();
    136   return NULL;
    137 }
    138 
    139 views::View* TrayPower::CreateNotificationView(user::LoginStatus status) {
    140   CHECK(notification_view_ == NULL);
    141   if (!PowerStatus::Get()->IsBatteryPresent())
    142     return NULL;
    143 
    144   notification_view_ = new PowerNotificationView(this);
    145   notification_view_->UpdateStatus();
    146 
    147   return notification_view_;
    148 }
    149 
    150 void TrayPower::DestroyTrayView() {
    151   power_tray_ = NULL;
    152 }
    153 
    154 void TrayPower::DestroyDefaultView() {
    155 }
    156 
    157 void TrayPower::DestroyNotificationView() {
    158   notification_view_ = NULL;
    159 }
    160 
    161 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) {
    162 }
    163 
    164 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
    165   SetTrayImageItemBorder(power_tray_, alignment);
    166 }
    167 
    168 void TrayPower::OnPowerStatusChanged() {
    169   RecordChargerType();
    170 
    171   if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) {
    172     ash::Shell::GetInstance()->system_tray_delegate()->
    173         ShowSpringChargerReplacementDialog();
    174   }
    175 
    176   bool battery_alert = UpdateNotificationState();
    177   if (power_tray_)
    178     power_tray_->UpdateStatus(battery_alert);
    179   if (notification_view_)
    180     notification_view_->UpdateStatus();
    181 
    182   // Factory testing may place the battery into unusual states.
    183   if (CommandLine::ForCurrentProcess()->HasSwitch(
    184           ash::switches::kAshHideNotificationsForFactory))
    185     return;
    186 
    187   if (ash::switches::UseUsbChargerNotification())
    188     MaybeShowUsbChargerNotification();
    189 
    190   if (battery_alert)
    191     ShowNotificationView();
    192   else if (notification_state_ == NOTIFICATION_NONE)
    193     HideNotificationView();
    194 
    195   usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected();
    196   line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected();
    197 }
    198 
    199 bool TrayPower::MaybeShowUsbChargerNotification() {
    200   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    201   const char kNotificationId[] = "usb-charger";
    202   bool usb_charger_is_connected = PowerStatus::Get()->IsUsbChargerConnected();
    203 
    204   // Check for a USB charger being connected.
    205   if (usb_charger_is_connected && !usb_charger_was_connected_) {
    206     scoped_ptr<Notification> notification(new Notification(
    207         message_center::NOTIFICATION_TYPE_SIMPLE,
    208         kNotificationId,
    209         rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE),
    210         rb.GetLocalizedString(
    211             IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT),
    212         rb.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER),
    213         base::string16(),
    214         message_center::NotifierId(
    215             message_center::NotifierId::SYSTEM_COMPONENT,
    216             system_notifier::kNotifierPower),
    217         message_center::RichNotificationData(),
    218         NULL));
    219     message_center_->AddNotification(notification.Pass());
    220     return true;
    221   }
    222 
    223   // Check for unplug of a USB charger while the USB charger notification is
    224   // showing.
    225   if (!usb_charger_is_connected && usb_charger_was_connected_) {
    226     message_center_->RemoveNotification(kNotificationId, false);
    227     return true;
    228   }
    229   return false;
    230 }
    231 
    232 bool TrayPower::UpdateNotificationState() {
    233   const PowerStatus& status = *PowerStatus::Get();
    234   if (!status.IsBatteryPresent() ||
    235       status.IsBatteryTimeBeingCalculated() ||
    236       status.IsMainsChargerConnected() ||
    237       status.IsOriginalSpringChargerConnected()) {
    238     notification_state_ = NOTIFICATION_NONE;
    239     return false;
    240   }
    241 
    242   return status.IsUsbChargerConnected() ?
    243       UpdateNotificationStateForRemainingPercentage() :
    244       UpdateNotificationStateForRemainingTime();
    245 }
    246 
    247 bool TrayPower::UpdateNotificationStateForRemainingTime() {
    248   // The notification includes a rounded minutes value, so round the estimate
    249   // received from the power manager to match.
    250   const int remaining_minutes = static_cast<int>(
    251       PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5);
    252 
    253   if (remaining_minutes >= kNoWarningMinutes ||
    254       PowerStatus::Get()->IsBatteryFull()) {
    255     notification_state_ = NOTIFICATION_NONE;
    256     return false;
    257   }
    258 
    259   switch (notification_state_) {
    260     case NOTIFICATION_NONE:
    261       if (remaining_minutes <= kCriticalMinutes) {
    262         notification_state_ = NOTIFICATION_CRITICAL;
    263         return true;
    264       }
    265       if (remaining_minutes <= kLowPowerMinutes) {
    266         notification_state_ = NOTIFICATION_LOW_POWER;
    267         return true;
    268       }
    269       return false;
    270     case NOTIFICATION_LOW_POWER:
    271       if (remaining_minutes <= kCriticalMinutes) {
    272         notification_state_ = NOTIFICATION_CRITICAL;
    273         return true;
    274       }
    275       return false;
    276     case NOTIFICATION_CRITICAL:
    277       return false;
    278   }
    279   NOTREACHED();
    280   return false;
    281 }
    282 
    283 bool TrayPower::UpdateNotificationStateForRemainingPercentage() {
    284   // The notification includes a rounded percentage, so round the value received
    285   // from the power manager to match.
    286   const int remaining_percentage =
    287       PowerStatus::Get()->GetRoundedBatteryPercent();
    288 
    289   if (remaining_percentage >= kNoWarningPercentage ||
    290       PowerStatus::Get()->IsBatteryFull()) {
    291     notification_state_ = NOTIFICATION_NONE;
    292     return false;
    293   }
    294 
    295   switch (notification_state_) {
    296     case NOTIFICATION_NONE:
    297       if (remaining_percentage <= kCriticalPercentage) {
    298         notification_state_ = NOTIFICATION_CRITICAL;
    299         return true;
    300       }
    301       if (remaining_percentage <= kLowPowerPercentage) {
    302         notification_state_ = NOTIFICATION_LOW_POWER;
    303         return true;
    304       }
    305       return false;
    306     case NOTIFICATION_LOW_POWER:
    307       if (remaining_percentage <= kCriticalPercentage) {
    308         notification_state_ = NOTIFICATION_CRITICAL;
    309         return true;
    310       }
    311       return false;
    312     case NOTIFICATION_CRITICAL:
    313       return false;
    314   }
    315   NOTREACHED();
    316   return false;
    317 }
    318 
    319 void TrayPower::RecordChargerType() {
    320   if (!PowerStatus::Get()->IsLinePowerConnected() ||
    321       line_power_was_connected_)
    322     return;
    323 
    324   ChargerType current_charger = UNKNOWN_CHARGER;
    325   if (PowerStatus::Get()->IsMainsChargerConnected()) {
    326     current_charger = MAINS_CHARGER;
    327   } else if (PowerStatus::Get()->IsUsbChargerConnected()) {
    328     current_charger = USB_CHARGER;
    329   } else if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) {
    330     current_charger =
    331         ash::Shell::GetInstance()->system_tray_delegate()->
    332             HasUserConfirmedSafeSpringCharger() ?
    333         SAFE_SPRING_CHARGER : UNCONFIRMED_SPRING_CHARGER;
    334   }
    335 
    336   if (current_charger != UNKNOWN_CHARGER) {
    337     UMA_HISTOGRAM_ENUMERATION("Power.ChargerType",
    338                               current_charger,
    339                               CHARGER_TYPE_COUNT);
    340   }
    341 }
    342 
    343 }  // namespace internal
    344 }  // namespace ash
    345