OLD | NEW |
| (Empty) |
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/common/system/chromeos/power/tray_power.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "ash/common/accessibility_delegate.h" | |
10 #include "ash/common/ash_switches.h" | |
11 #include "ash/common/system/chromeos/devicetype_utils.h" | |
12 #include "ash/common/system/chromeos/power/battery_notification.h" | |
13 #include "ash/common/system/chromeos/power/dual_role_notification.h" | |
14 #include "ash/common/system/date/date_view.h" | |
15 #include "ash/common/system/system_notifier.h" | |
16 #include "ash/common/system/tray/system_tray_delegate.h" | |
17 #include "ash/common/system/tray/tray_constants.h" | |
18 #include "ash/common/system/tray/tray_item_view.h" | |
19 #include "ash/common/system/tray/tray_utils.h" | |
20 #include "ash/resources/grit/ash_resources.h" | |
21 #include "ash/strings/grit/ash_strings.h" | |
22 #include "base/command_line.h" | |
23 #include "base/logging.h" | |
24 #include "base/metrics/histogram.h" | |
25 #include "base/time/time.h" | |
26 #include "ui/accessibility/ax_node_data.h" | |
27 #include "ui/base/resource/resource_bundle.h" | |
28 #include "ui/message_center/message_center.h" | |
29 #include "ui/message_center/notification.h" | |
30 #include "ui/message_center/notification_delegate.h" | |
31 #include "ui/views/controls/image_view.h" | |
32 #include "ui/views/view.h" | |
33 | |
34 using message_center::MessageCenter; | |
35 using message_center::Notification; | |
36 | |
37 namespace ash { | |
38 | |
39 // Informs the TrayPower instance when a USB notification is closed. | |
40 class UsbNotificationDelegate : public message_center::NotificationDelegate { | |
41 public: | |
42 explicit UsbNotificationDelegate(TrayPower* tray_power) | |
43 : tray_power_(tray_power) {} | |
44 | |
45 // Overridden from message_center::NotificationDelegate. | |
46 void Close(bool by_user) override { | |
47 if (by_user) | |
48 tray_power_->NotifyUsbNotificationClosedByUser(); | |
49 } | |
50 | |
51 private: | |
52 ~UsbNotificationDelegate() override {} | |
53 | |
54 TrayPower* tray_power_; | |
55 | |
56 DISALLOW_COPY_AND_ASSIGN(UsbNotificationDelegate); | |
57 }; | |
58 | |
59 namespace { | |
60 | |
61 std::string GetNotificationStateString( | |
62 TrayPower::NotificationState notification_state) { | |
63 switch (notification_state) { | |
64 case TrayPower::NOTIFICATION_NONE: | |
65 return "none"; | |
66 case TrayPower::NOTIFICATION_LOW_POWER: | |
67 return "low power"; | |
68 case TrayPower::NOTIFICATION_CRITICAL: | |
69 return "critical power"; | |
70 } | |
71 NOTREACHED() << "Unknown state " << notification_state; | |
72 return "Unknown state"; | |
73 } | |
74 | |
75 void LogBatteryForUsbCharger(TrayPower::NotificationState state, | |
76 int battery_percent) { | |
77 LOG(WARNING) << "Showing " << GetNotificationStateString(state) | |
78 << " notification. USB charger is connected. " | |
79 << "Battery percentage: " << battery_percent << "%."; | |
80 } | |
81 | |
82 void LogBatteryForNoCharger(TrayPower::NotificationState state, | |
83 int remaining_minutes) { | |
84 LOG(WARNING) << "Showing " << GetNotificationStateString(state) | |
85 << " notification. No charger connected." | |
86 << " Remaining time: " << remaining_minutes << " minutes."; | |
87 } | |
88 | |
89 } // namespace | |
90 | |
91 namespace tray { | |
92 | |
93 // This view is used only for the tray. | |
94 class PowerTrayView : public TrayItemView { | |
95 public: | |
96 explicit PowerTrayView(SystemTrayItem* owner) : TrayItemView(owner) { | |
97 CreateImageView(); | |
98 UpdateImage(); | |
99 } | |
100 | |
101 ~PowerTrayView() override {} | |
102 | |
103 // Overriden from views::View. | |
104 void GetAccessibleNodeData(ui::AXNodeData* node_data) override { | |
105 node_data->SetName(accessible_name_); | |
106 node_data->role = ui::AX_ROLE_BUTTON; | |
107 } | |
108 | |
109 void UpdateStatus(bool battery_alert) { | |
110 UpdateImage(); | |
111 SetVisible(PowerStatus::Get()->IsBatteryPresent()); | |
112 | |
113 if (battery_alert) { | |
114 accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(true); | |
115 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); | |
116 } | |
117 } | |
118 | |
119 private: | |
120 void UpdateImage() { | |
121 const PowerStatus::BatteryImageInfo info = | |
122 PowerStatus::Get()->GetBatteryImageInfo(PowerStatus::ICON_LIGHT); | |
123 if (info != previous_image_info_) { | |
124 image_view()->SetImage(PowerStatus::Get()->GetBatteryImage(info)); | |
125 previous_image_info_ = info; | |
126 } | |
127 } | |
128 | |
129 base::string16 accessible_name_; | |
130 | |
131 // Information about the last-used image. Cached to avoid unnecessary updates | |
132 // (http://crbug.com/589348). | |
133 PowerStatus::BatteryImageInfo previous_image_info_; | |
134 | |
135 DISALLOW_COPY_AND_ASSIGN(PowerTrayView); | |
136 }; | |
137 | |
138 } // namespace tray | |
139 | |
140 const int TrayPower::kCriticalMinutes = 5; | |
141 const int TrayPower::kLowPowerMinutes = 15; | |
142 const int TrayPower::kNoWarningMinutes = 30; | |
143 const int TrayPower::kCriticalPercentage = 5; | |
144 const int TrayPower::kLowPowerPercentage = 10; | |
145 const int TrayPower::kNoWarningPercentage = 15; | |
146 | |
147 const char TrayPower::kUsbNotificationId[] = "usb-charger"; | |
148 | |
149 TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center) | |
150 : SystemTrayItem(system_tray, UMA_POWER), | |
151 message_center_(message_center), | |
152 power_tray_(NULL), | |
153 notification_state_(NOTIFICATION_NONE), | |
154 usb_charger_was_connected_(false), | |
155 line_power_was_connected_(false), | |
156 usb_notification_dismissed_(false) { | |
157 PowerStatus::Get()->AddObserver(this); | |
158 } | |
159 | |
160 TrayPower::~TrayPower() { | |
161 PowerStatus::Get()->RemoveObserver(this); | |
162 message_center_->RemoveNotification(kUsbNotificationId, false); | |
163 } | |
164 | |
165 views::View* TrayPower::CreateTrayView(LoginStatus status) { | |
166 // There may not be enough information when this is created about whether | |
167 // there is a battery or not. So always create this, and adjust visibility as | |
168 // necessary. | |
169 CHECK(power_tray_ == NULL); | |
170 power_tray_ = new tray::PowerTrayView(this); | |
171 power_tray_->UpdateStatus(false); | |
172 return power_tray_; | |
173 } | |
174 | |
175 views::View* TrayPower::CreateDefaultView(LoginStatus status) { | |
176 // Make sure icon status is up to date. (Also triggers stub activation). | |
177 PowerStatus::Get()->RequestStatusUpdate(); | |
178 return NULL; | |
179 } | |
180 | |
181 void TrayPower::DestroyTrayView() { | |
182 power_tray_ = NULL; | |
183 } | |
184 | |
185 void TrayPower::DestroyDefaultView() {} | |
186 | |
187 void TrayPower::UpdateAfterLoginStatusChange(LoginStatus status) {} | |
188 | |
189 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { | |
190 SetTrayImageItemBorder(power_tray_, alignment); | |
191 } | |
192 | |
193 void TrayPower::OnPowerStatusChanged() { | |
194 bool battery_alert = UpdateNotificationState(); | |
195 if (power_tray_) | |
196 power_tray_->UpdateStatus(battery_alert); | |
197 | |
198 // Factory testing may place the battery into unusual states. | |
199 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | |
200 ash::switches::kAshHideNotificationsForFactory)) | |
201 return; | |
202 | |
203 MaybeShowUsbChargerNotification(); | |
204 MaybeShowDualRoleNotification(); | |
205 | |
206 if (battery_alert) { | |
207 // Remove any existing notification so it's dismissed before adding a new | |
208 // one. Otherwise we might update a "low battery" notification to "critical" | |
209 // without it being shown again. | |
210 battery_notification_.reset(); | |
211 battery_notification_.reset( | |
212 new BatteryNotification(message_center_, notification_state_)); | |
213 } else if (notification_state_ == NOTIFICATION_NONE) { | |
214 battery_notification_.reset(); | |
215 } else if (battery_notification_.get()) { | |
216 battery_notification_->Update(notification_state_); | |
217 } | |
218 | |
219 usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected(); | |
220 line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected(); | |
221 } | |
222 | |
223 bool TrayPower::MaybeShowUsbChargerNotification() { | |
224 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
225 const PowerStatus& status = *PowerStatus::Get(); | |
226 | |
227 bool usb_charger_is_connected = status.IsUsbChargerConnected(); | |
228 | |
229 // Check for a USB charger being connected. | |
230 if (usb_charger_is_connected && !usb_charger_was_connected_ && | |
231 !usb_notification_dismissed_) { | |
232 std::unique_ptr<Notification> notification(new Notification( | |
233 message_center::NOTIFICATION_TYPE_SIMPLE, kUsbNotificationId, | |
234 rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE), | |
235 ash::SubstituteChromeOSDeviceType( | |
236 IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT), | |
237 rb.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER), | |
238 base::string16(), GURL(), | |
239 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, | |
240 system_notifier::kNotifierPower), | |
241 message_center::RichNotificationData(), | |
242 new UsbNotificationDelegate(this))); | |
243 message_center_->AddNotification(std::move(notification)); | |
244 return true; | |
245 } else if (!usb_charger_is_connected && usb_charger_was_connected_) { | |
246 // USB charger was unplugged or was identified as a different type while | |
247 // the USB charger notification was showing. | |
248 message_center_->RemoveNotification(kUsbNotificationId, false); | |
249 if (!status.IsLinePowerConnected()) | |
250 usb_notification_dismissed_ = false; | |
251 return true; | |
252 } | |
253 return false; | |
254 } | |
255 | |
256 void TrayPower::MaybeShowDualRoleNotification() { | |
257 const PowerStatus& status = *PowerStatus::Get(); | |
258 if (!status.HasDualRoleDevices()) { | |
259 dual_role_notification_.reset(); | |
260 return; | |
261 } | |
262 | |
263 if (!dual_role_notification_) | |
264 dual_role_notification_.reset(new DualRoleNotification(message_center_)); | |
265 dual_role_notification_->Update(); | |
266 } | |
267 | |
268 bool TrayPower::UpdateNotificationState() { | |
269 const PowerStatus& status = *PowerStatus::Get(); | |
270 if (!status.IsBatteryPresent() || status.IsBatteryTimeBeingCalculated() || | |
271 status.IsMainsChargerConnected()) { | |
272 notification_state_ = NOTIFICATION_NONE; | |
273 return false; | |
274 } | |
275 | |
276 return status.IsUsbChargerConnected() | |
277 ? UpdateNotificationStateForRemainingPercentage() | |
278 : UpdateNotificationStateForRemainingTime(); | |
279 } | |
280 | |
281 bool TrayPower::UpdateNotificationStateForRemainingTime() { | |
282 // The notification includes a rounded minutes value, so round the estimate | |
283 // received from the power manager to match. | |
284 const int remaining_minutes = static_cast<int>( | |
285 PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5); | |
286 | |
287 if (remaining_minutes >= kNoWarningMinutes || | |
288 PowerStatus::Get()->IsBatteryFull()) { | |
289 notification_state_ = NOTIFICATION_NONE; | |
290 return false; | |
291 } | |
292 | |
293 switch (notification_state_) { | |
294 case NOTIFICATION_NONE: | |
295 if (remaining_minutes <= kCriticalMinutes) { | |
296 notification_state_ = NOTIFICATION_CRITICAL; | |
297 LogBatteryForNoCharger(notification_state_, remaining_minutes); | |
298 return true; | |
299 } | |
300 if (remaining_minutes <= kLowPowerMinutes) { | |
301 notification_state_ = NOTIFICATION_LOW_POWER; | |
302 LogBatteryForNoCharger(notification_state_, remaining_minutes); | |
303 return true; | |
304 } | |
305 return false; | |
306 case NOTIFICATION_LOW_POWER: | |
307 if (remaining_minutes <= kCriticalMinutes) { | |
308 notification_state_ = NOTIFICATION_CRITICAL; | |
309 LogBatteryForNoCharger(notification_state_, remaining_minutes); | |
310 return true; | |
311 } | |
312 return false; | |
313 case NOTIFICATION_CRITICAL: | |
314 return false; | |
315 } | |
316 NOTREACHED(); | |
317 return false; | |
318 } | |
319 | |
320 bool TrayPower::UpdateNotificationStateForRemainingPercentage() { | |
321 // The notification includes a rounded percentage, so round the value received | |
322 // from the power manager to match. | |
323 const int remaining_percentage = | |
324 PowerStatus::Get()->GetRoundedBatteryPercent(); | |
325 | |
326 if (remaining_percentage >= kNoWarningPercentage || | |
327 PowerStatus::Get()->IsBatteryFull()) { | |
328 notification_state_ = NOTIFICATION_NONE; | |
329 return false; | |
330 } | |
331 | |
332 switch (notification_state_) { | |
333 case NOTIFICATION_NONE: | |
334 if (remaining_percentage <= kCriticalPercentage) { | |
335 notification_state_ = NOTIFICATION_CRITICAL; | |
336 LogBatteryForUsbCharger(notification_state_, remaining_percentage); | |
337 return true; | |
338 } | |
339 if (remaining_percentage <= kLowPowerPercentage) { | |
340 notification_state_ = NOTIFICATION_LOW_POWER; | |
341 LogBatteryForUsbCharger(notification_state_, remaining_percentage); | |
342 return true; | |
343 } | |
344 return false; | |
345 case NOTIFICATION_LOW_POWER: | |
346 if (remaining_percentage <= kCriticalPercentage) { | |
347 notification_state_ = NOTIFICATION_CRITICAL; | |
348 LogBatteryForUsbCharger(notification_state_, remaining_percentage); | |
349 return true; | |
350 } | |
351 return false; | |
352 case NOTIFICATION_CRITICAL: | |
353 return false; | |
354 } | |
355 NOTREACHED(); | |
356 return false; | |
357 } | |
358 | |
359 void TrayPower::NotifyUsbNotificationClosedByUser() { | |
360 usb_notification_dismissed_ = true; | |
361 } | |
362 | |
363 } // namespace ash | |
OLD | NEW |