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/accessibility_delegate.h" 8 #include "ash/ash_switches.h" 9 #include "ash/shell.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" 36 37 using message_center::MessageCenter; 38 using message_center::Notification; 39 40 namespace ash { 41 namespace tray { 42 namespace { 43 44 const int kMaxSpringChargerAccessibilityNotifyCount = 3; 45 const int kSpringChargerAccessibilityTimerFirstTimeNotifyInSeconds = 30; 46 const int kSpringChargerAccessibilityTimerRepeatInMinutes = 5; 47 48 } 49 50 // This view is used only for the tray. 51 class PowerTrayView : public views::ImageView { 52 public: 53 PowerTrayView() 54 : spring_charger_spoken_notification_count_(0) { 55 UpdateImage(); 56 } 57 58 virtual ~PowerTrayView() { 59 } 60 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; 65 } 66 67 void UpdateStatus(bool battery_alert) { 68 UpdateImage(); 69 SetVisible(PowerStatus::Get()->IsBatteryPresent()); 70 71 if (battery_alert) { 72 accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(); 73 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); 74 } 75 } 76 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); 84 } 85 86 private: 87 void UpdateImage() { 88 SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT)); 89 } 90 91 void NotifyChargerIssue() { 92 if (!Shell::GetInstance()->accessibility_delegate()-> 93 IsSpokenFeedbackEnabled()) 94 return; 95 96 if (!Shell::GetInstance()->system_tray_delegate()-> 97 IsSpringChargerReplacementDialogVisible()) { 98 spring_charger_accessility_timer_.Stop(); 99 return; 100 } 101 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_; 106 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(); 118 } 119 } 120 121 base::string16 accessible_name_; 122 123 // Tracks how many times the original spring charger accessibility 124 // notification has been spoken. 125 int spring_charger_spoken_notification_count_; 126 127 base::RepeatingTimer<PowerTrayView> spring_charger_accessility_timer_; 128 129 DISALLOW_COPY_AND_ASSIGN(PowerTrayView); 130 }; 131 132 class PowerNotificationView : public TrayNotificationView { 133 public: 134 explicit PowerNotificationView(TrayPower* owner) 135 : TrayNotificationView(owner, 0) { 136 power_status_view_ = 137 new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true); 138 InitView(power_status_view_); 139 } 140 141 void UpdateStatus() { 142 SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK)); 143 } 144 145 private: 146 PowerStatusView* power_status_view_; 147 148 DISALLOW_COPY_AND_ASSIGN(PowerNotificationView); 149 }; 150 151 } // namespace tray 152 153 using tray::PowerNotificationView; 154 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; 161 162 TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center) 163 : SystemTrayItem(system_tray), 164 message_center_(message_center), 165 power_tray_(NULL), 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); 171 } 172 173 TrayPower::~TrayPower() { 174 PowerStatus::Get()->RemoveObserver(this); 175 } 176 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 180 // necessary. 181 CHECK(power_tray_ == NULL); 182 power_tray_ = new tray::PowerTrayView(); 183 power_tray_->UpdateStatus(false); 184 return power_tray_; 185 } 186 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(); 190 return NULL; 191 } 192 193 views::View* TrayPower::CreateNotificationView(user::LoginStatus status) { 194 CHECK(notification_view_ == NULL); 195 if (!PowerStatus::Get()->IsBatteryPresent()) 196 return NULL; 197 198 notification_view_ = new PowerNotificationView(this); 199 notification_view_->UpdateStatus(); 200 201 return notification_view_; 202 } 203 204 void TrayPower::DestroyTrayView() { 205 power_tray_ = NULL; 206 } 207 208 void TrayPower::DestroyDefaultView() { 209 } 210 211 void TrayPower::DestroyNotificationView() { 212 notification_view_ = NULL; 213 } 214 215 void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) { 216 } 217 218 void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { 219 SetTrayImageItemBorder(power_tray_, alignment); 220 } 221 222 void TrayPower::OnPowerStatusChanged() { 223 RecordChargerType(); 224 225 if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) { 226 if (ash::Shell::GetInstance()->system_tray_delegate()-> 227 ShowSpringChargerReplacementDialog()) { 228 power_tray_->SetupNotifyBadCharger(); 229 } 230 } 231 232 bool battery_alert = UpdateNotificationState(); 233 if (power_tray_) 234 power_tray_->UpdateStatus(battery_alert); 235 if (notification_view_) 236 notification_view_->UpdateStatus(); 237 238 // Factory testing may place the battery into unusual states. 239 if (CommandLine::ForCurrentProcess()->HasSwitch( 240 ash::switches::kAshHideNotificationsForFactory)) 241 return; 242 243 MaybeShowUsbChargerNotification(); 244 245 if (battery_alert) 246 ShowNotificationView(); 247 else if (notification_state_ == NOTIFICATION_NONE) 248 HideNotificationView(); 249 250 usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected(); 251 line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected(); 252 } 253 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(); 258 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, 263 kNotificationId, 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), 268 base::string16(), 269 message_center::NotifierId( 270 message_center::NotifierId::SYSTEM_COMPONENT, 271 system_notifier::kNotifierPower), 272 message_center::RichNotificationData(), 273 NULL)); 274 message_center_->AddNotification(notification.Pass()); 275 return true; 276 } 277 278 // Check for unplug of a USB charger while the USB charger notification is 279 // showing. 280 if (!usb_charger_is_connected && usb_charger_was_connected_) { 281 message_center_->RemoveNotification(kNotificationId, false); 282 return true; 283 } 284 return false; 285 } 286 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; 294 return false; 295 } 296 297 return status.IsUsbChargerConnected() ? 298 UpdateNotificationStateForRemainingPercentage() : 299 UpdateNotificationStateForRemainingTime(); 300 } 301 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); 307 308 if (remaining_minutes >= kNoWarningMinutes || 309 PowerStatus::Get()->IsBatteryFull()) { 310 notification_state_ = NOTIFICATION_NONE; 311 return false; 312 } 313 314 switch (notification_state_) { 315 case NOTIFICATION_NONE: 316 if (remaining_minutes <= kCriticalMinutes) { 317 notification_state_ = NOTIFICATION_CRITICAL; 318 return true; 319 } 320 if (remaining_minutes <= kLowPowerMinutes) { 321 notification_state_ = NOTIFICATION_LOW_POWER; 322 return true; 323 } 324 return false; 325 case NOTIFICATION_LOW_POWER: 326 if (remaining_minutes <= kCriticalMinutes) { 327 notification_state_ = NOTIFICATION_CRITICAL; 328 return true; 329 } 330 return false; 331 case NOTIFICATION_CRITICAL: 332 return false; 333 } 334 NOTREACHED(); 335 return false; 336 } 337 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(); 343 344 if (remaining_percentage >= kNoWarningPercentage || 345 PowerStatus::Get()->IsBatteryFull()) { 346 notification_state_ = NOTIFICATION_NONE; 347 return false; 348 } 349 350 switch (notification_state_) { 351 case NOTIFICATION_NONE: 352 if (remaining_percentage <= kCriticalPercentage) { 353 notification_state_ = NOTIFICATION_CRITICAL; 354 return true; 355 } 356 if (remaining_percentage <= kLowPowerPercentage) { 357 notification_state_ = NOTIFICATION_LOW_POWER; 358 return true; 359 } 360 return false; 361 case NOTIFICATION_LOW_POWER: 362 if (remaining_percentage <= kCriticalPercentage) { 363 notification_state_ = NOTIFICATION_CRITICAL; 364 return true; 365 } 366 return false; 367 case NOTIFICATION_CRITICAL: 368 return false; 369 } 370 NOTREACHED(); 371 return false; 372 } 373 374 void TrayPower::RecordChargerType() { 375 if (!PowerStatus::Get()->IsLinePowerConnected() || 376 line_power_was_connected_) 377 return; 378 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()) { 385 current_charger = 386 ash::Shell::GetInstance()->system_tray_delegate()-> 387 HasUserConfirmedSafeSpringCharger() ? 388 SAFE_SPRING_CHARGER : UNCONFIRMED_SPRING_CHARGER; 389 } 390 391 if (current_charger != UNKNOWN_CHARGER) { 392 UMA_HISTOGRAM_ENUMERATION("Power.ChargerType", 393 current_charger, 394 CHARGER_TYPE_COUNT); 395 } 396 } 397 398 } // namespace ash 399