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