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/devicetype_utils.h"
11 #include "ash/system/chromeos/power/battery_notification.h"
12 #include "ash/system/date/date_view.h"
13 #include "ash/system/system_notifier.h"
14 #include "ash/system/tray/system_tray_delegate.h"
15 #include "ash/system/tray/tray_constants.h"
16 #include "ash/system/tray/tray_utils.h"
17 #include "base/command_line.h"
18 #include "base/logging.h"
19 #include "base/metrics/histogram.h"
20 #include "base/time/time.h"
21 #include "grit/ash_resources.h"
22 #include "grit/ash_strings.h"
23 #include "ui/accessibility/ax_view_state.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/message_center/message_center.h"
26 #include "ui/message_center/notification.h"
27 #include "ui/views/controls/image_view.h"
28 #include "ui/views/view.h"
30 using message_center::MessageCenter
;
31 using message_center::Notification
;
36 std::string
GetNotificationStateString(
37 TrayPower::NotificationState notification_state
) {
38 switch (notification_state
) {
39 case TrayPower::NOTIFICATION_NONE
:
41 case TrayPower::NOTIFICATION_LOW_POWER
:
43 case TrayPower::NOTIFICATION_CRITICAL
:
44 return "critical power";
46 NOTREACHED() << "Unknown state " << notification_state
;
47 return "Unknown state";
50 void LogBatteryForUsbCharger(TrayPower::NotificationState state
,
51 int battery_percent
) {
52 LOG(WARNING
) << "Showing " << GetNotificationStateString(state
)
53 << " notification. USB charger is connected. "
54 << "Battery percentage: " << battery_percent
<< "%.";
57 void LogBatteryForNoCharger(TrayPower::NotificationState state
,
58 int remaining_minutes
) {
59 LOG(WARNING
) << "Showing " << GetNotificationStateString(state
)
60 << " notification. No charger connected."
61 << " Remaining time: " << remaining_minutes
<< " minutes.";
68 // This view is used only for the tray.
69 class PowerTrayView
: public views::ImageView
{
75 ~PowerTrayView() override
{}
77 // Overriden from views::View.
78 void GetAccessibleState(ui::AXViewState
* state
) override
{
79 state
->name
= accessible_name_
;
80 state
->role
= ui::AX_ROLE_BUTTON
;
83 void UpdateStatus(bool battery_alert
) {
85 SetVisible(PowerStatus::Get()->IsBatteryPresent());
88 accessible_name_
= PowerStatus::Get()->GetAccessibleNameString(true);
89 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, true);
95 SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT
));
98 base::string16 accessible_name_
;
100 DISALLOW_COPY_AND_ASSIGN(PowerTrayView
);
105 const int TrayPower::kCriticalMinutes
= 5;
106 const int TrayPower::kLowPowerMinutes
= 15;
107 const int TrayPower::kNoWarningMinutes
= 30;
108 const int TrayPower::kCriticalPercentage
= 5;
109 const int TrayPower::kLowPowerPercentage
= 10;
110 const int TrayPower::kNoWarningPercentage
= 15;
112 TrayPower::TrayPower(SystemTray
* system_tray
, MessageCenter
* message_center
)
113 : SystemTrayItem(system_tray
),
114 message_center_(message_center
),
116 notification_state_(NOTIFICATION_NONE
),
117 usb_charger_was_connected_(false),
118 line_power_was_connected_(false) {
119 PowerStatus::Get()->AddObserver(this);
122 TrayPower::~TrayPower() {
123 PowerStatus::Get()->RemoveObserver(this);
126 views::View
* TrayPower::CreateTrayView(user::LoginStatus status
) {
127 // There may not be enough information when this is created about whether
128 // there is a battery or not. So always create this, and adjust visibility as
130 CHECK(power_tray_
== NULL
);
131 power_tray_
= new tray::PowerTrayView();
132 power_tray_
->UpdateStatus(false);
136 views::View
* TrayPower::CreateDefaultView(user::LoginStatus status
) {
137 // Make sure icon status is up-to-date. (Also triggers stub activation).
138 PowerStatus::Get()->RequestStatusUpdate();
142 void TrayPower::DestroyTrayView() {
146 void TrayPower::DestroyDefaultView() {
149 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status
) {
152 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment
) {
153 SetTrayImageItemBorder(power_tray_
, alignment
);
156 void TrayPower::OnPowerStatusChanged() {
157 bool battery_alert
= UpdateNotificationState();
159 power_tray_
->UpdateStatus(battery_alert
);
161 // Factory testing may place the battery into unusual states.
162 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
163 ash::switches::kAshHideNotificationsForFactory
))
166 MaybeShowUsbChargerNotification();
169 // Remove any existing notification so it's dismissed before adding a new
170 // one. Otherwise we might update a "low battery" notification to "critical"
171 // without it being shown again.
172 battery_notification_
.reset();
173 battery_notification_
.reset(
174 new BatteryNotification(message_center_
, notification_state_
));
175 } else if (notification_state_
== NOTIFICATION_NONE
) {
176 battery_notification_
.reset();
177 } else if (battery_notification_
.get()) {
178 battery_notification_
->Update(notification_state_
);
181 usb_charger_was_connected_
= PowerStatus::Get()->IsUsbChargerConnected();
182 line_power_was_connected_
= PowerStatus::Get()->IsLinePowerConnected();
185 bool TrayPower::MaybeShowUsbChargerNotification() {
186 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
187 const char kNotificationId
[] = "usb-charger";
188 bool usb_charger_is_connected
= PowerStatus::Get()->IsUsbChargerConnected();
190 // Check for a USB charger being connected.
191 if (usb_charger_is_connected
&& !usb_charger_was_connected_
) {
192 scoped_ptr
<Notification
> notification(new Notification(
193 message_center::NOTIFICATION_TYPE_SIMPLE
,
195 rb
.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE
),
196 ash::SubstituteChromeOSDeviceType(
197 IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT
),
198 rb
.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER
),
200 message_center::NotifierId(
201 message_center::NotifierId::SYSTEM_COMPONENT
,
202 system_notifier::kNotifierPower
),
203 message_center::RichNotificationData(),
205 message_center_
->AddNotification(notification
.Pass());
209 // Check for unplug of a USB charger while the USB charger notification is
211 if (!usb_charger_is_connected
&& usb_charger_was_connected_
) {
212 message_center_
->RemoveNotification(kNotificationId
, false);
218 bool TrayPower::UpdateNotificationState() {
219 const PowerStatus
& status
= *PowerStatus::Get();
220 if (!status
.IsBatteryPresent() ||
221 status
.IsBatteryTimeBeingCalculated() ||
222 status
.IsMainsChargerConnected()) {
223 notification_state_
= NOTIFICATION_NONE
;
227 return status
.IsUsbChargerConnected() ?
228 UpdateNotificationStateForRemainingPercentage() :
229 UpdateNotificationStateForRemainingTime();
232 bool TrayPower::UpdateNotificationStateForRemainingTime() {
233 // The notification includes a rounded minutes value, so round the estimate
234 // received from the power manager to match.
235 const int remaining_minutes
= static_cast<int>(
236 PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5);
238 if (remaining_minutes
>= kNoWarningMinutes
||
239 PowerStatus::Get()->IsBatteryFull()) {
240 notification_state_
= NOTIFICATION_NONE
;
244 switch (notification_state_
) {
245 case NOTIFICATION_NONE
:
246 if (remaining_minutes
<= kCriticalMinutes
) {
247 notification_state_
= NOTIFICATION_CRITICAL
;
248 LogBatteryForNoCharger(notification_state_
, remaining_minutes
);
251 if (remaining_minutes
<= kLowPowerMinutes
) {
252 notification_state_
= NOTIFICATION_LOW_POWER
;
253 LogBatteryForNoCharger(notification_state_
, remaining_minutes
);
257 case NOTIFICATION_LOW_POWER
:
258 if (remaining_minutes
<= kCriticalMinutes
) {
259 notification_state_
= NOTIFICATION_CRITICAL
;
260 LogBatteryForNoCharger(notification_state_
, remaining_minutes
);
264 case NOTIFICATION_CRITICAL
:
271 bool TrayPower::UpdateNotificationStateForRemainingPercentage() {
272 // The notification includes a rounded percentage, so round the value received
273 // from the power manager to match.
274 const int remaining_percentage
=
275 PowerStatus::Get()->GetRoundedBatteryPercent();
277 if (remaining_percentage
>= kNoWarningPercentage
||
278 PowerStatus::Get()->IsBatteryFull()) {
279 notification_state_
= NOTIFICATION_NONE
;
283 switch (notification_state_
) {
284 case NOTIFICATION_NONE
:
285 if (remaining_percentage
<= kCriticalPercentage
) {
286 notification_state_
= NOTIFICATION_CRITICAL
;
287 LogBatteryForUsbCharger(notification_state_
, remaining_percentage
);
290 if (remaining_percentage
<= kLowPowerPercentage
) {
291 notification_state_
= NOTIFICATION_LOW_POWER
;
292 LogBatteryForUsbCharger(notification_state_
, remaining_percentage
);
296 case NOTIFICATION_LOW_POWER
:
297 if (remaining_percentage
<= kCriticalPercentage
) {
298 notification_state_
= NOTIFICATION_CRITICAL
;
299 LogBatteryForUsbCharger(notification_state_
, remaining_percentage
);
303 case NOTIFICATION_CRITICAL
: