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:
PowerTrayView()45 PowerTrayView() {
46 UpdateImage();
47 }
48
~PowerTrayView()49 virtual ~PowerTrayView() {
50 }
51
52 // Overriden from views::View.
GetAccessibleState(ui::AccessibleViewState * state)53 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
54 state->name = accessible_name_;
55 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
56 }
57
UpdateStatus(bool battery_alert)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:
UpdateImage()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:
PowerNotificationView(TrayPower * owner)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
UpdateStatus()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
TrayPower(SystemTray * system_tray,MessageCenter * message_center)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
~TrayPower()119 TrayPower::~TrayPower() {
120 PowerStatus::Get()->RemoveObserver(this);
121 }
122
CreateTrayView(user::LoginStatus status)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
CreateDefaultView(user::LoginStatus status)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
CreateNotificationView(user::LoginStatus status)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
DestroyTrayView()150 void TrayPower::DestroyTrayView() {
151 power_tray_ = NULL;
152 }
153
DestroyDefaultView()154 void TrayPower::DestroyDefaultView() {
155 }
156
DestroyNotificationView()157 void TrayPower::DestroyNotificationView() {
158 notification_view_ = NULL;
159 }
160
UpdateAfterLoginStatusChange(user::LoginStatus status)161 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) {
162 }
163
UpdateAfterShelfAlignmentChange(ShelfAlignment alignment)164 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
165 SetTrayImageItemBorder(power_tray_, alignment);
166 }
167
OnPowerStatusChanged()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
MaybeShowUsbChargerNotification()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
UpdateNotificationState()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
UpdateNotificationStateForRemainingTime()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
UpdateNotificationStateForRemainingPercentage()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
RecordChargerType()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