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/base/accessibility/accessible_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;
46 const int kMaxSpringChargerAccessibilityNotifyCount = 3;
47 const int kSpringChargerAccessibilityTimerFirstTimeNotifyInSeconds = 30;
48 const int kSpringChargerAccessibilityTimerRepeatInMinutes = 5;
52 // This view is used only for the tray.
53 class PowerTrayView : public views::ImageView {
56 : spring_charger_spoken_notification_count_(0) {
60 virtual ~PowerTrayView() {
63 // Overriden from views::View.
64 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
65 state->name = accessible_name_;
66 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
69 void UpdateStatus(bool battery_alert) {
71 SetVisible(PowerStatus::Get()->IsBatteryPresent());
74 accessible_name_ = PowerStatus::Get()->GetAccessibleNameString();
75 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
79 void SetupNotifyBadCharger() {
80 // Poll with a shorter duration timer to notify the charger issue
81 // for the first time after the charger dialog is displayed.
82 spring_charger_accessility_timer_.Start(
83 FROM_HERE, base::TimeDelta::FromSeconds(
84 kSpringChargerAccessibilityTimerFirstTimeNotifyInSeconds),
85 this, &PowerTrayView::NotifyChargerIssue);
90 SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT));
93 void NotifyChargerIssue() {
94 if (!Shell::GetInstance()->accessibility_delegate()->
95 IsSpokenFeedbackEnabled())
98 if (!Shell::GetInstance()->system_tray_delegate()->
99 IsSpringChargerReplacementDialogVisible()) {
100 spring_charger_accessility_timer_.Stop();
104 accessible_name_ = ui::ResourceBundle::GetSharedInstance().
105 GetLocalizedString(IDS_CHARGER_REPLACEMENT_ACCESSIBILTY_NOTIFICATION);
106 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
107 ++spring_charger_spoken_notification_count_;
109 if (spring_charger_spoken_notification_count_ == 1) {
110 // After notify the charger issue for the first time, repeat the
111 // notification with a longer duration timer.
112 spring_charger_accessility_timer_.Stop();
113 spring_charger_accessility_timer_.Start(
114 FROM_HERE, base::TimeDelta::FromMinutes(
115 kSpringChargerAccessibilityTimerRepeatInMinutes),
116 this, &PowerTrayView::NotifyChargerIssue);
117 } else if (spring_charger_spoken_notification_count_ >=
118 kMaxSpringChargerAccessibilityNotifyCount) {
119 spring_charger_accessility_timer_.Stop();
123 base::string16 accessible_name_;
125 // Tracks how many times the original spring charger accessibility
126 // notification has been spoken.
127 int spring_charger_spoken_notification_count_;
129 base::RepeatingTimer<PowerTrayView> spring_charger_accessility_timer_;
131 DISALLOW_COPY_AND_ASSIGN(PowerTrayView);
134 class PowerNotificationView : public TrayNotificationView {
136 explicit PowerNotificationView(TrayPower* owner)
137 : TrayNotificationView(owner, 0) {
139 new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true);
140 InitView(power_status_view_);
143 void UpdateStatus() {
144 SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK));
148 PowerStatusView* power_status_view_;
150 DISALLOW_COPY_AND_ASSIGN(PowerNotificationView);
155 using tray::PowerNotificationView;
157 const int TrayPower::kCriticalMinutes = 5;
158 const int TrayPower::kLowPowerMinutes = 15;
159 const int TrayPower::kNoWarningMinutes = 30;
160 const int TrayPower::kCriticalPercentage = 5;
161 const int TrayPower::kLowPowerPercentage = 10;
162 const int TrayPower::kNoWarningPercentage = 15;
164 TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center)
165 : SystemTrayItem(system_tray),
166 message_center_(message_center),
168 notification_view_(NULL),
169 notification_state_(NOTIFICATION_NONE),
170 usb_charger_was_connected_(false),
171 line_power_was_connected_(false) {
172 PowerStatus::Get()->AddObserver(this);
175 TrayPower::~TrayPower() {
176 PowerStatus::Get()->RemoveObserver(this);
179 views::View* TrayPower::CreateTrayView(user::LoginStatus status) {
180 // There may not be enough information when this is created about whether
181 // there is a battery or not. So always create this, and adjust visibility as
183 CHECK(power_tray_ == NULL);
184 power_tray_ = new tray::PowerTrayView();
185 power_tray_->UpdateStatus(false);
189 views::View* TrayPower::CreateDefaultView(user::LoginStatus status) {
190 // Make sure icon status is up-to-date. (Also triggers stub activation).
191 PowerStatus::Get()->RequestStatusUpdate();
195 views::View* TrayPower::CreateNotificationView(user::LoginStatus status) {
196 CHECK(notification_view_ == NULL);
197 if (!PowerStatus::Get()->IsBatteryPresent())
200 notification_view_ = new PowerNotificationView(this);
201 notification_view_->UpdateStatus();
203 return notification_view_;
206 void TrayPower::DestroyTrayView() {
210 void TrayPower::DestroyDefaultView() {
213 void TrayPower::DestroyNotificationView() {
214 notification_view_ = NULL;
217 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) {
220 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
221 SetTrayImageItemBorder(power_tray_, alignment);
224 void TrayPower::OnPowerStatusChanged() {
227 if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) {
228 if (ash::Shell::GetInstance()->system_tray_delegate()->
229 ShowSpringChargerReplacementDialog()) {
230 power_tray_->SetupNotifyBadCharger();
234 bool battery_alert = UpdateNotificationState();
236 power_tray_->UpdateStatus(battery_alert);
237 if (notification_view_)
238 notification_view_->UpdateStatus();
240 // Factory testing may place the battery into unusual states.
241 if (CommandLine::ForCurrentProcess()->HasSwitch(
242 ash::switches::kAshHideNotificationsForFactory))
245 if (ash::switches::UseUsbChargerNotification())
246 MaybeShowUsbChargerNotification();
249 ShowNotificationView();
250 else if (notification_state_ == NOTIFICATION_NONE)
251 HideNotificationView();
253 usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected();
254 line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected();
257 bool TrayPower::MaybeShowUsbChargerNotification() {
258 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
259 const char kNotificationId[] = "usb-charger";
260 bool usb_charger_is_connected = PowerStatus::Get()->IsUsbChargerConnected();
262 // Check for a USB charger being connected.
263 if (usb_charger_is_connected && !usb_charger_was_connected_) {
264 scoped_ptr<Notification> notification(new Notification(
265 message_center::NOTIFICATION_TYPE_SIMPLE,
267 rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE),
268 rb.GetLocalizedString(
269 IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT),
270 rb.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER),
272 message_center::NotifierId(
273 message_center::NotifierId::SYSTEM_COMPONENT,
274 system_notifier::kNotifierPower),
275 message_center::RichNotificationData(),
277 message_center_->AddNotification(notification.Pass());
281 // Check for unplug of a USB charger while the USB charger notification is
283 if (!usb_charger_is_connected && usb_charger_was_connected_) {
284 message_center_->RemoveNotification(kNotificationId, false);
290 bool TrayPower::UpdateNotificationState() {
291 const PowerStatus& status = *PowerStatus::Get();
292 if (!status.IsBatteryPresent() ||
293 status.IsBatteryTimeBeingCalculated() ||
294 status.IsMainsChargerConnected() ||
295 status.IsOriginalSpringChargerConnected()) {
296 notification_state_ = NOTIFICATION_NONE;
300 return status.IsUsbChargerConnected() ?
301 UpdateNotificationStateForRemainingPercentage() :
302 UpdateNotificationStateForRemainingTime();
305 bool TrayPower::UpdateNotificationStateForRemainingTime() {
306 // The notification includes a rounded minutes value, so round the estimate
307 // received from the power manager to match.
308 const int remaining_minutes = static_cast<int>(
309 PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5);
311 if (remaining_minutes >= kNoWarningMinutes ||
312 PowerStatus::Get()->IsBatteryFull()) {
313 notification_state_ = NOTIFICATION_NONE;
317 switch (notification_state_) {
318 case NOTIFICATION_NONE:
319 if (remaining_minutes <= kCriticalMinutes) {
320 notification_state_ = NOTIFICATION_CRITICAL;
323 if (remaining_minutes <= kLowPowerMinutes) {
324 notification_state_ = NOTIFICATION_LOW_POWER;
328 case NOTIFICATION_LOW_POWER:
329 if (remaining_minutes <= kCriticalMinutes) {
330 notification_state_ = NOTIFICATION_CRITICAL;
334 case NOTIFICATION_CRITICAL:
341 bool TrayPower::UpdateNotificationStateForRemainingPercentage() {
342 // The notification includes a rounded percentage, so round the value received
343 // from the power manager to match.
344 const int remaining_percentage =
345 PowerStatus::Get()->GetRoundedBatteryPercent();
347 if (remaining_percentage >= kNoWarningPercentage ||
348 PowerStatus::Get()->IsBatteryFull()) {
349 notification_state_ = NOTIFICATION_NONE;
353 switch (notification_state_) {
354 case NOTIFICATION_NONE:
355 if (remaining_percentage <= kCriticalPercentage) {
356 notification_state_ = NOTIFICATION_CRITICAL;
359 if (remaining_percentage <= kLowPowerPercentage) {
360 notification_state_ = NOTIFICATION_LOW_POWER;
364 case NOTIFICATION_LOW_POWER:
365 if (remaining_percentage <= kCriticalPercentage) {
366 notification_state_ = NOTIFICATION_CRITICAL;
370 case NOTIFICATION_CRITICAL:
377 void TrayPower::RecordChargerType() {
378 if (!PowerStatus::Get()->IsLinePowerConnected() ||
379 line_power_was_connected_)
382 ChargerType current_charger = UNKNOWN_CHARGER;
383 if (PowerStatus::Get()->IsMainsChargerConnected()) {
384 current_charger = MAINS_CHARGER;
385 } else if (PowerStatus::Get()->IsUsbChargerConnected()) {
386 current_charger = USB_CHARGER;
387 } else if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) {
389 ash::Shell::GetInstance()->system_tray_delegate()->
390 HasUserConfirmedSafeSpringCharger() ?
391 SAFE_SPRING_CHARGER : UNCONFIRMED_SPRING_CHARGER;
394 if (current_charger != UNKNOWN_CHARGER) {
395 UMA_HISTOGRAM_ENUMERATION("Power.ChargerType",
401 } // namespace internal