tray_power.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 3f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// found in the LICENSE file. 4f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 5f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/chromeos/power/tray_power.h" 6f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 7f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/accessibility_delegate.h" 8f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/ash_switches.h" 9f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/shell.h" 10f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/chromeos/power/power_status_view.h" 11f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/date/date_view.h" 12f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/system_notifier.h" 13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/tray/system_tray_delegate.h" 14f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/tray/tray_constants.h" 15f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/tray/tray_notification_view.h" 16f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ash/system/tray/tray_utils.h" 17f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/command_line.h" 18f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/metrics/histogram.h" 19f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/time/time.h" 20f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "grit/ash_resources.h" 21f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "grit/ash_strings.h" 22f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "third_party/icu/source/i18n/unicode/fieldpos.h" 23f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "third_party/icu/source/i18n/unicode/fmtable.h" 24f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/accessibility/ax_view_state.h" 25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h" 26f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/message_center/message_center.h" 27f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/message_center/notification.h" 28f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/controls/button/button.h" 29f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/controls/image_view.h" 30f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/controls/label.h" 31f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/layout/box_layout.h" 32f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/layout/fill_layout.h" 33f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/layout/grid_layout.h" 34f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/view.h" 35f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/widget/widget.h" 36f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 37f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using message_center::MessageCenter; 38f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using message_center::Notification; 39f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 40f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace ash { 41f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace internal { 42f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace tray { 43f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 44f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace { 45f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 46f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)const int kMaxSpringChargerAccessibilityNotifyCount = 3; 47f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)const int kSpringChargerAccessibilityTimerFirstTimeNotifyInSeconds = 30; 48f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)const int kSpringChargerAccessibilityTimerRepeatInMinutes = 5; 49f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)} 51f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 52f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// This view is used only for the tray. 53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class PowerTrayView : public views::ImageView { 54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) public: 55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) PowerTrayView() 56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) : spring_charger_spoken_notification_count_(0) { 57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) UpdateImage(); 58f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 60f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) virtual ~PowerTrayView() { 61f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 62f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 63f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // Overriden from views::View. 64f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE { 65f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) state->name = accessible_name_; 66f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) state->role = ui::AX_ROLE_BUTTON; 67f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 68f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 69f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) void UpdateStatus(bool battery_alert) { 70f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) UpdateImage(); 71f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) SetVisible(PowerStatus::Get()->IsBatteryPresent()); 72f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 73f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) if (battery_alert) { 74f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(); 75f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); 76f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 77f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 78f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) void SetupNotifyBadCharger() { 80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // Poll with a shorter duration timer to notify the charger issue 81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) // for the first time after the charger dialog is displayed. 82f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) spring_charger_accessility_timer_.Start( 83f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) FROM_HERE, base::TimeDelta::FromSeconds( 84f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) kSpringChargerAccessibilityTimerFirstTimeNotifyInSeconds), 85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) this, &PowerTrayView::NotifyChargerIssue); 86f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 88f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) private: 89f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) void UpdateImage() { 90f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT)); 91f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 92f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 93 void NotifyChargerIssue() { 94 if (!Shell::GetInstance()->accessibility_delegate()-> 95 IsSpokenFeedbackEnabled()) 96 return; 97 98 if (!Shell::GetInstance()->system_tray_delegate()-> 99 IsSpringChargerReplacementDialogVisible()) { 100 spring_charger_accessility_timer_.Stop(); 101 return; 102 } 103 104 accessible_name_ = ui::ResourceBundle::GetSharedInstance(). 105 GetLocalizedString(IDS_CHARGER_REPLACEMENT_ACCESSIBILTY_NOTIFICATION); 106 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); 107 ++spring_charger_spoken_notification_count_; 108 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(); 120 } 121 } 122 123 base::string16 accessible_name_; 124 125 // Tracks how many times the original spring charger accessibility 126 // notification has been spoken. 127 int spring_charger_spoken_notification_count_; 128 129 base::RepeatingTimer<PowerTrayView> spring_charger_accessility_timer_; 130 131 DISALLOW_COPY_AND_ASSIGN(PowerTrayView); 132}; 133 134class PowerNotificationView : public TrayNotificationView { 135 public: 136 explicit PowerNotificationView(TrayPower* owner) 137 : TrayNotificationView(owner, 0) { 138 power_status_view_ = 139 new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true); 140 InitView(power_status_view_); 141 } 142 143 void UpdateStatus() { 144 SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK)); 145 } 146 147 private: 148 PowerStatusView* power_status_view_; 149 150 DISALLOW_COPY_AND_ASSIGN(PowerNotificationView); 151}; 152 153} // namespace tray 154 155using tray::PowerNotificationView; 156 157const int TrayPower::kCriticalMinutes = 5; 158const int TrayPower::kLowPowerMinutes = 15; 159const int TrayPower::kNoWarningMinutes = 30; 160const int TrayPower::kCriticalPercentage = 5; 161const int TrayPower::kLowPowerPercentage = 10; 162const int TrayPower::kNoWarningPercentage = 15; 163 164TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center) 165 : SystemTrayItem(system_tray), 166 message_center_(message_center), 167 power_tray_(NULL), 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); 173} 174 175TrayPower::~TrayPower() { 176 PowerStatus::Get()->RemoveObserver(this); 177} 178 179views::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 182 // necessary. 183 CHECK(power_tray_ == NULL); 184 power_tray_ = new tray::PowerTrayView(); 185 power_tray_->UpdateStatus(false); 186 return power_tray_; 187} 188 189views::View* TrayPower::CreateDefaultView(user::LoginStatus status) { 190 // Make sure icon status is up-to-date. (Also triggers stub activation). 191 PowerStatus::Get()->RequestStatusUpdate(); 192 return NULL; 193} 194 195views::View* TrayPower::CreateNotificationView(user::LoginStatus status) { 196 CHECK(notification_view_ == NULL); 197 if (!PowerStatus::Get()->IsBatteryPresent()) 198 return NULL; 199 200 notification_view_ = new PowerNotificationView(this); 201 notification_view_->UpdateStatus(); 202 203 return notification_view_; 204} 205 206void TrayPower::DestroyTrayView() { 207 power_tray_ = NULL; 208} 209 210void TrayPower::DestroyDefaultView() { 211} 212 213void TrayPower::DestroyNotificationView() { 214 notification_view_ = NULL; 215} 216 217void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) { 218} 219 220void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { 221 SetTrayImageItemBorder(power_tray_, alignment); 222} 223 224void TrayPower::OnPowerStatusChanged() { 225 RecordChargerType(); 226 227 if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) { 228 if (ash::Shell::GetInstance()->system_tray_delegate()-> 229 ShowSpringChargerReplacementDialog()) { 230 power_tray_->SetupNotifyBadCharger(); 231 } 232 } 233 234 bool battery_alert = UpdateNotificationState(); 235 if (power_tray_) 236 power_tray_->UpdateStatus(battery_alert); 237 if (notification_view_) 238 notification_view_->UpdateStatus(); 239 240 // Factory testing may place the battery into unusual states. 241 if (CommandLine::ForCurrentProcess()->HasSwitch( 242 ash::switches::kAshHideNotificationsForFactory)) 243 return; 244 245 MaybeShowUsbChargerNotification(); 246 247 if (battery_alert) 248 ShowNotificationView(); 249 else if (notification_state_ == NOTIFICATION_NONE) 250 HideNotificationView(); 251 252 usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected(); 253 line_power_was_connected_ = PowerStatus::Get()->IsLinePowerConnected(); 254} 255 256bool TrayPower::MaybeShowUsbChargerNotification() { 257 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 258 const char kNotificationId[] = "usb-charger"; 259 bool usb_charger_is_connected = PowerStatus::Get()->IsUsbChargerConnected(); 260 261 // Check for a USB charger being connected. 262 if (usb_charger_is_connected && !usb_charger_was_connected_) { 263 scoped_ptr<Notification> notification(new Notification( 264 message_center::NOTIFICATION_TYPE_SIMPLE, 265 kNotificationId, 266 rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE), 267 rb.GetLocalizedString( 268 IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE_SHORT), 269 rb.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER), 270 base::string16(), 271 message_center::NotifierId( 272 message_center::NotifierId::SYSTEM_COMPONENT, 273 system_notifier::kNotifierPower), 274 message_center::RichNotificationData(), 275 NULL)); 276 message_center_->AddNotification(notification.Pass()); 277 return true; 278 } 279 280 // Check for unplug of a USB charger while the USB charger notification is 281 // showing. 282 if (!usb_charger_is_connected && usb_charger_was_connected_) { 283 message_center_->RemoveNotification(kNotificationId, false); 284 return true; 285 } 286 return false; 287} 288 289bool TrayPower::UpdateNotificationState() { 290 const PowerStatus& status = *PowerStatus::Get(); 291 if (!status.IsBatteryPresent() || 292 status.IsBatteryTimeBeingCalculated() || 293 status.IsMainsChargerConnected() || 294 status.IsOriginalSpringChargerConnected()) { 295 notification_state_ = NOTIFICATION_NONE; 296 return false; 297 } 298 299 return status.IsUsbChargerConnected() ? 300 UpdateNotificationStateForRemainingPercentage() : 301 UpdateNotificationStateForRemainingTime(); 302} 303 304bool TrayPower::UpdateNotificationStateForRemainingTime() { 305 // The notification includes a rounded minutes value, so round the estimate 306 // received from the power manager to match. 307 const int remaining_minutes = static_cast<int>( 308 PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5); 309 310 if (remaining_minutes >= kNoWarningMinutes || 311 PowerStatus::Get()->IsBatteryFull()) { 312 notification_state_ = NOTIFICATION_NONE; 313 return false; 314 } 315 316 switch (notification_state_) { 317 case NOTIFICATION_NONE: 318 if (remaining_minutes <= kCriticalMinutes) { 319 notification_state_ = NOTIFICATION_CRITICAL; 320 return true; 321 } 322 if (remaining_minutes <= kLowPowerMinutes) { 323 notification_state_ = NOTIFICATION_LOW_POWER; 324 return true; 325 } 326 return false; 327 case NOTIFICATION_LOW_POWER: 328 if (remaining_minutes <= kCriticalMinutes) { 329 notification_state_ = NOTIFICATION_CRITICAL; 330 return true; 331 } 332 return false; 333 case NOTIFICATION_CRITICAL: 334 return false; 335 } 336 NOTREACHED(); 337 return false; 338} 339 340bool TrayPower::UpdateNotificationStateForRemainingPercentage() { 341 // The notification includes a rounded percentage, so round the value received 342 // from the power manager to match. 343 const int remaining_percentage = 344 PowerStatus::Get()->GetRoundedBatteryPercent(); 345 346 if (remaining_percentage >= kNoWarningPercentage || 347 PowerStatus::Get()->IsBatteryFull()) { 348 notification_state_ = NOTIFICATION_NONE; 349 return false; 350 } 351 352 switch (notification_state_) { 353 case NOTIFICATION_NONE: 354 if (remaining_percentage <= kCriticalPercentage) { 355 notification_state_ = NOTIFICATION_CRITICAL; 356 return true; 357 } 358 if (remaining_percentage <= kLowPowerPercentage) { 359 notification_state_ = NOTIFICATION_LOW_POWER; 360 return true; 361 } 362 return false; 363 case NOTIFICATION_LOW_POWER: 364 if (remaining_percentage <= kCriticalPercentage) { 365 notification_state_ = NOTIFICATION_CRITICAL; 366 return true; 367 } 368 return false; 369 case NOTIFICATION_CRITICAL: 370 return false; 371 } 372 NOTREACHED(); 373 return false; 374} 375 376void TrayPower::RecordChargerType() { 377 if (!PowerStatus::Get()->IsLinePowerConnected() || 378 line_power_was_connected_) 379 return; 380 381 ChargerType current_charger = UNKNOWN_CHARGER; 382 if (PowerStatus::Get()->IsMainsChargerConnected()) { 383 current_charger = MAINS_CHARGER; 384 } else if (PowerStatus::Get()->IsUsbChargerConnected()) { 385 current_charger = USB_CHARGER; 386 } else if (PowerStatus::Get()->IsOriginalSpringChargerConnected()) { 387 current_charger = 388 ash::Shell::GetInstance()->system_tray_delegate()-> 389 HasUserConfirmedSafeSpringCharger() ? 390 SAFE_SPRING_CHARGER : UNCONFIRMED_SPRING_CHARGER; 391 } 392 393 if (current_charger != UNKNOWN_CHARGER) { 394 UMA_HISTOGRAM_ENUMERATION("Power.ChargerType", 395 current_charger, 396 CHARGER_TYPE_COUNT); 397 } 398} 399 400} // namespace internal 401} // namespace ash 402