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.
5 #include "ash/system/chromeos/power/tray_power.h"
7 #include "ash/accessibility_delegate.h"
8 #include "ash/ash_switches.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"
37 using message_center::MessageCenter
;
38 using message_center::Notification
;
44 const int kMaxSpringChargerAccessibilityNotifyCount
= 3;
45 const int kSpringChargerAccessibilityTimerFirstTimeNotifyInSeconds
= 30;
46 const int kSpringChargerAccessibilityTimerRepeatInMinutes
= 5;
50 // This view is used only for the tray.
51 class PowerTrayView
: public views::ImageView
{
54 : spring_charger_spoken_notification_count_(0) {
58 virtual ~PowerTrayView() {
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
;
67 void UpdateStatus(bool battery_alert
) {
69 SetVisible(PowerStatus::Get()->IsBatteryPresent());
72 accessible_name_
= PowerStatus::Get()->GetAccessibleNameString(true);
73 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, true);
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
);
88 SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT
));
91 void NotifyChargerIssue() {
92 if (!Shell::GetInstance()->accessibility_delegate()->
93 IsSpokenFeedbackEnabled())
96 if (!Shell::GetInstance()->system_tray_delegate()->
97 IsSpringChargerReplacementDialogVisible()) {
98 spring_charger_accessility_timer_
.Stop();
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_
;
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();
121 base::string16 accessible_name_
;
123 // Tracks how many times the original spring charger accessibility
124 // notification has been spoken.
125 int spring_charger_spoken_notification_count_
;
127 base::RepeatingTimer
<PowerTrayView
> spring_charger_accessility_timer_
;
129 DISALLOW_COPY_AND_ASSIGN(PowerTrayView
);
132 class PowerNotificationView
: public TrayNotificationView
{
134 explicit PowerNotificationView(TrayPower
* owner
)
135 : TrayNotificationView(owner
, 0) {
137 new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION
, true);
138 InitView(power_status_view_
);
141 void UpdateStatus() {
142 SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK
));
146 PowerStatusView
* power_status_view_
;
148 DISALLOW_COPY_AND_ASSIGN(PowerNotificationView
);
153 using tray::PowerNotificationView
;
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;
162 TrayPower::TrayPower(SystemTray
* system_tray
, MessageCenter
* message_center
)
163 : SystemTrayItem(system_tray
),
164 message_center_(message_center
),
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);
173 TrayPower::~TrayPower() {
174 PowerStatus::Get()->RemoveObserver(this);
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
181 CHECK(power_tray_
== NULL
);
182 power_tray_
= new tray::PowerTrayView();
183 power_tray_
->UpdateStatus(false);
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();
193 views::View
* TrayPower::CreateNotificationView(user::LoginStatus status
) {
194 CHECK(notification_view_
== NULL
);
195 if (!PowerStatus::Get()->IsBatteryPresent())
198 notification_view_
= new PowerNotificationView(this);
199 notification_view_
->UpdateStatus();
201 return notification_view_
;
204 void TrayPower::DestroyTrayView() {
208 void TrayPower::DestroyDefaultView() {
211 void TrayPower::DestroyNotificationView() {
212 notification_view_
= NULL
;
215 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status
) {
218 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment
) {
219 SetTrayImageItemBorder(power_tray_
, alignment
);
222 void TrayPower::OnPowerStatusChanged() {
225 if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) {
226 if (ash::Shell::GetInstance()->system_tray_delegate()->
227 ShowSpringChargerReplacementDialog()) {
228 power_tray_
->SetupNotifyBadCharger();
232 bool battery_alert
= UpdateNotificationState();
234 power_tray_
->UpdateStatus(battery_alert
);
235 if (notification_view_
)
236 notification_view_
->UpdateStatus();
238 // Factory testing may place the battery into unusual states.
239 if (CommandLine::ForCurrentProcess()->HasSwitch(
240 ash::switches::kAshHideNotificationsForFactory
))
243 MaybeShowUsbChargerNotification();
246 ShowNotificationView();
247 else if (notification_state_
== NOTIFICATION_NONE
)
248 HideNotificationView();
250 usb_charger_was_connected_
= PowerStatus::Get()->IsUsbChargerConnected();
251 line_power_was_connected_
= PowerStatus::Get()->IsLinePowerConnected();
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();
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
,
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
),
269 message_center::NotifierId(
270 message_center::NotifierId::SYSTEM_COMPONENT
,
271 system_notifier::kNotifierPower
),
272 message_center::RichNotificationData(),
274 message_center_
->AddNotification(notification
.Pass());
278 // Check for unplug of a USB charger while the USB charger notification is
280 if (!usb_charger_is_connected
&& usb_charger_was_connected_
) {
281 message_center_
->RemoveNotification(kNotificationId
, false);
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
;
297 return status
.IsUsbChargerConnected() ?
298 UpdateNotificationStateForRemainingPercentage() :
299 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);
308 if (remaining_minutes
>= kNoWarningMinutes
||
309 PowerStatus::Get()->IsBatteryFull()) {
310 notification_state_
= NOTIFICATION_NONE
;
314 switch (notification_state_
) {
315 case NOTIFICATION_NONE
:
316 if (remaining_minutes
<= kCriticalMinutes
) {
317 notification_state_
= NOTIFICATION_CRITICAL
;
320 if (remaining_minutes
<= kLowPowerMinutes
) {
321 notification_state_
= NOTIFICATION_LOW_POWER
;
325 case NOTIFICATION_LOW_POWER
:
326 if (remaining_minutes
<= kCriticalMinutes
) {
327 notification_state_
= NOTIFICATION_CRITICAL
;
331 case NOTIFICATION_CRITICAL
:
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();
344 if (remaining_percentage
>= kNoWarningPercentage
||
345 PowerStatus::Get()->IsBatteryFull()) {
346 notification_state_
= NOTIFICATION_NONE
;
350 switch (notification_state_
) {
351 case NOTIFICATION_NONE
:
352 if (remaining_percentage
<= kCriticalPercentage
) {
353 notification_state_
= NOTIFICATION_CRITICAL
;
356 if (remaining_percentage
<= kLowPowerPercentage
) {
357 notification_state_
= NOTIFICATION_LOW_POWER
;
361 case NOTIFICATION_LOW_POWER
:
362 if (remaining_percentage
<= kCriticalPercentage
) {
363 notification_state_
= NOTIFICATION_CRITICAL
;
367 case NOTIFICATION_CRITICAL
:
374 void TrayPower::RecordChargerType() {
375 if (!PowerStatus::Get()->IsLinePowerConnected() ||
376 line_power_was_connected_
)
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()) {
386 ash::Shell::GetInstance()->system_tray_delegate()->
387 HasUserConfirmedSafeSpringCharger() ?
388 SAFE_SPRING_CHARGER
: UNCONFIRMED_SPRING_CHARGER
;
391 if (current_charger
!= UNKNOWN_CHARGER
) {
392 UMA_HISTOGRAM_ENUMERATION("Power.ChargerType",