• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
PowerTrayView()53   PowerTrayView()
54       : spring_charger_spoken_notification_count_(0) {
55     UpdateImage();
56   }
57 
~PowerTrayView()58   virtual ~PowerTrayView() {
59   }
60 
61   // Overriden from views::View.
GetAccessibleState(ui::AXViewState * state)62   virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
63     state->name = accessible_name_;
64     state->role = ui::AX_ROLE_BUTTON;
65   }
66 
UpdateStatus(bool battery_alert)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 
SetupNotifyBadCharger()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:
UpdateImage()87   void UpdateImage() {
88     SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT));
89   }
90 
NotifyChargerIssue()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:
PowerNotificationView(TrayPower * owner)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 
UpdateStatus()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 
TrayPower(SystemTray * system_tray,MessageCenter * message_center)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 
~TrayPower()173 TrayPower::~TrayPower() {
174   PowerStatus::Get()->RemoveObserver(this);
175 }
176 
CreateTrayView(user::LoginStatus status)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 
CreateDefaultView(user::LoginStatus status)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 
CreateNotificationView(user::LoginStatus status)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 
DestroyTrayView()204 void TrayPower::DestroyTrayView() {
205   power_tray_ = NULL;
206 }
207 
DestroyDefaultView()208 void TrayPower::DestroyDefaultView() {
209 }
210 
DestroyNotificationView()211 void TrayPower::DestroyNotificationView() {
212   notification_view_ = NULL;
213 }
214 
UpdateAfterLoginStatusChange(user::LoginStatus status)215 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) {
216 }
217 
UpdateAfterShelfAlignmentChange(ShelfAlignment alignment)218 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
219   SetTrayImageItemBorder(power_tray_, alignment);
220 }
221 
OnPowerStatusChanged()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 
MaybeShowUsbChargerNotification()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 
UpdateNotificationState()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 
UpdateNotificationStateForRemainingTime()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 
UpdateNotificationStateForRemainingPercentage()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 
RecordChargerType()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